[libidw-java] 02/04: Imported Upstream version 1.6.1

Felix Natter fnatter-guest at alioth.debian.org
Sat Aug 17 18:15:21 UTC 2013


This is an automated email from the git hooks/post-receive script.

fnatter-guest pushed a commit to branch master
in repository libidw-java.

commit 2310624c5b90ac9bf39893ecaa7bebe9bff91b03
Author: Felix Natter <fnatter at gmx.net>
Date:   Tue May 21 19:51:55 2013 +0200

    Imported Upstream version 1.6.1
---
 LICENSE.txt                                        |  340 ++++
 src/net/infonode/docking/AbstractTabWindow.java    |  616 ++++++
 .../infonode/docking/DefaultButtonFactories.java   |   97 +
 src/net/infonode/docking/DockingWindow.java        | 2080 ++++++++++++++++++++
 src/net/infonode/docking/DockingWindowAdapter.java |   85 +
 .../infonode/docking/DockingWindowListener.java    |  261 +++
 .../docking/DockingWindowsReleaseInfo.java         |   56 +
 src/net/infonode/docking/FloatingWindow.java       |  600 ++++++
 src/net/infonode/docking/FocusManager.java         |  184 ++
 .../docking/OperationAbortedException.java         |   66 +
 .../infonode/docking/RectangleBorderComponent.java |   54 +
 src/net/infonode/docking/RootWindow.java           | 1305 ++++++++++++
 src/net/infonode/docking/SplitWindow.java          |  400 ++++
 src/net/infonode/docking/TabWindow.java            |  276 +++
 src/net/infonode/docking/TabWindowHoverAction.java |  185 ++
 src/net/infonode/docking/TabWindowMover.java       |   76 +
 src/net/infonode/docking/View.java                 |  551 ++++++
 src/net/infonode/docking/ViewSerializer.java       |   59 +
 src/net/infonode/docking/WindowBar.java            |  300 +++
 src/net/infonode/docking/WindowDecoder.java        |  109 +
 src/net/infonode/docking/WindowDragger.java        |  164 ++
 src/net/infonode/docking/WindowIds.java            |   37 +
 .../infonode/docking/WindowPopupMenuFactory.java   |   42 +
 src/net/infonode/docking/WindowTab.java            |  222 +++
 src/net/infonode/docking/WindowTabState.java       |   65 +
 .../docking/action/CloseOthersWindowAction.java    |   91 +
 .../infonode/docking/action/CloseWindowAction.java |   79 +
 .../docking/action/CloseWithAbortWindowAction.java |   76 +
 .../infonode/docking/action/DockWindowAction.java  |   74 +
 .../docking/action/DockWithAbortWindowAction.java  |   80 +
 .../docking/action/DockingWindowAction.java        |  104 +
 .../action/DockingWindowActionProperty.java        |   70 +
 .../docking/action/MaximizeWindowAction.java       |   83 +
 .../action/MaximizeWithAbortWindowAction.java      |   89 +
 .../action/MinimizeWithAbortWindowAction.java      |   80 +
 .../infonode/docking/action/NullWindowAction.java  |   63 +
 .../docking/action/RestoreFocusWindowAction.java   |   65 +
 .../docking/action/RestoreParentWindowAction.java  |   93 +
 .../action/RestoreParentWithAbortWindowAction.java |   99 +
 .../action/RestoreViewWithAbortTitleBarAction.java |   95 +
 .../action/RestoreWithAbortWindowAction.java       |   82 +
 .../docking/action/StateDependentWindowAction.java |   99 +
 .../docking/action/UndockWindowAction.java         |   85 +
 .../action/UndockWithAbortWindowAction.java        |   85 +
 src/net/infonode/docking/action/package.html       |    5 +
 .../docking/drag/DockingWindowDragSource.java      |  111 ++
 .../docking/drag/DockingWindowDragger.java         |   73 +
 .../docking/drag/DockingWindowDraggerProvider.java |   43 +
 src/net/infonode/docking/drag/package.html         |    5 +
 .../infonode/docking/drop/AcceptAllDropFilter.java |   48 +
 src/net/infonode/docking/drop/ChildDropInfo.java   |   73 +
 src/net/infonode/docking/drop/DropFilter.java      |   62 +
 .../infonode/docking/drop/DropFilterProperty.java  |   70 +
 src/net/infonode/docking/drop/DropInfo.java        |   84 +
 .../infonode/docking/drop/InsertTabDropInfo.java   |   49 +
 .../infonode/docking/drop/InteriorDropInfo.java    |   52 +
 .../infonode/docking/drop/RejectAllDropFilter.java |   48 +
 src/net/infonode/docking/drop/SplitDropInfo.java   |   63 +
 src/net/infonode/docking/drop/package.html         |    5 +
 src/net/infonode/docking/info/Info.java            |   49 +
 .../docking/internal/HeavyWeightContainer.java     |   79 +
 .../docking/internal/HeavyWeightDragRectangle.java |   85 +
 src/net/infonode/docking/internal/ReadContext.java |   71 +
 .../infonode/docking/internal/ViewTitleBar.java    |  293 +++
 .../infonode/docking/internal/WindowAncestors.java |   54 +
 .../infonode/docking/internal/WriteContext.java    |   64 +
 .../docking/internalutil/AbstractButtonInfo.java   |   42 +
 .../infonode/docking/internalutil/ButtonInfo.java  |   34 +
 .../docking/internalutil/CloseButtonInfo.java      |   36 +
 .../docking/internalutil/DockButtonInfo.java       |   35 +
 .../infonode/docking/internalutil/DropAction.java  |   47 +
 .../docking/internalutil/InternalDockingUtil.java  |  195 ++
 .../docking/internalutil/MaximizeButtonInfo.java   |   37 +
 .../docking/internalutil/MinimizeButtonInfo.java   |   37 +
 .../docking/internalutil/RestoreButtonInfo.java    |   37 +
 .../docking/internalutil/UndockButtonInfo.java     |   35 +
 .../docking/location/AbstractWindowLocation.java   |   93 +
 .../infonode/docking/location/LocationDecoder.java |   64 +
 .../infonode/docking/location/NullLocation.java    |   56 +
 .../infonode/docking/location/WindowLocation.java  |   39 +
 .../docking/location/WindowRootLocation.java       |   63 +
 .../docking/location/WindowSplitLocation.java      |   73 +
 .../docking/location/WindowTabLocation.java        |   66 +
 .../docking/model/AbstractTabWindowItem.java       |   69 +
 .../infonode/docking/model/FloatingWindowItem.java |   59 +
 src/net/infonode/docking/model/RootWindowItem.java |   75 +
 .../infonode/docking/model/SplitWindowItem.java    |  136 ++
 src/net/infonode/docking/model/TabWindowItem.java  |  100 +
 src/net/infonode/docking/model/ViewItem.java       |   79 +
 src/net/infonode/docking/model/ViewReader.java     |   50 +
 src/net/infonode/docking/model/ViewWriter.java     |   40 +
 src/net/infonode/docking/model/WindowBarItem.java  |   74 +
 src/net/infonode/docking/model/WindowItem.java     |  346 ++++
 .../infonode/docking/model/WindowItemDecoder.java  |   61 +
 .../DockingWindowActionMouseButtonListener.java    |  137 ++
 src/net/infonode/docking/package.html              |    5 +
 .../DockingWindowDropFilterProperties.java         |  213 ++
 .../properties/DockingWindowProperties.java        |  461 +++++
 .../properties/FloatingWindowProperties.java       |  195 ++
 .../docking/properties/RootWindowProperties.java   |  942 +++++++++
 .../docking/properties/SplitWindowProperties.java  |  234 +++
 .../docking/properties/TabWindowProperties.java    |  308 +++
 .../docking/properties/ViewProperties.java         |  217 ++
 .../docking/properties/ViewTitleBarProperties.java |  346 ++++
 .../properties/ViewTitleBarStateProperties.java    |  461 +++++
 .../docking/properties/WindowBarProperties.java    |  296 +++
 .../properties/WindowTabButtonProperties.java      |  296 +++
 .../docking/properties/WindowTabProperties.java    |  196 ++
 .../properties/WindowTabStateProperties.java       |  190 ++
 src/net/infonode/docking/properties/package.html   |    5 +
 .../docking/theme/BlueHighlightDockingTheme.java   |   97 +
 .../docking/theme/ClassicDockingTheme.java         |  143 ++
 .../docking/theme/DefaultDockingTheme.java         |   52 +
 .../docking/theme/DockingWindowsTheme.java         |   62 +
 .../docking/theme/GradientDockingTheme.java        |  229 +++
 .../docking/theme/LookAndFeelDockingTheme.java     |  340 ++++
 .../docking/theme/ShapedGradientDockingTheme.java  |  282 +++
 .../docking/theme/SlimFlatDockingTheme.java        |  149 ++
 .../docking/theme/SoftBlueIceDockingTheme.java     |  140 ++
 .../theme/internal/laftheme/TitleBarUI.java        |  495 +++++
 .../internal/laftheme/TitleBarUIListener.java      |   30 +
 .../resource/xp/bar_button_close_hovered.png       |  Bin 0 -> 457 bytes
 .../resource/xp/bar_button_close_normal.png        |  Bin 0 -> 505 bytes
 .../resource/xp/bar_button_close_pressed.png       |  Bin 0 -> 505 bytes
 .../resource/xp/bar_button_dock_hovered.png        |  Bin 0 -> 431 bytes
 .../resource/xp/bar_button_dock_normal.png         |  Bin 0 -> 474 bytes
 .../resource/xp/bar_button_dock_pressed.png        |  Bin 0 -> 478 bytes
 .../resource/xp/bar_button_maximize_hovered.png    |  Bin 0 -> 413 bytes
 .../resource/xp/bar_button_maximize_normal.png     |  Bin 0 -> 436 bytes
 .../resource/xp/bar_button_maximize_pressed.png    |  Bin 0 -> 437 bytes
 .../resource/xp/bar_button_minimize_hovered.png    |  Bin 0 -> 402 bytes
 .../resource/xp/bar_button_minimize_normal.png     |  Bin 0 -> 440 bytes
 .../resource/xp/bar_button_minimize_pressed.png    |  Bin 0 -> 435 bytes
 .../resource/xp/bar_button_restore_hovered.png     |  Bin 0 -> 433 bytes
 .../resource/xp/bar_button_restore_normal.png      |  Bin 0 -> 475 bytes
 .../resource/xp/bar_button_restore_pressed.png     |  Bin 0 -> 465 bytes
 .../resource/xp/bar_button_undock_hovered.png      |  Bin 0 -> 437 bytes
 .../resource/xp/bar_button_undock_normal.png       |  Bin 0 -> 473 bytes
 .../resource/xp/bar_button_undock_pressed.png      |  Bin 0 -> 467 bytes
 .../internal/resource/xp/bartab_hovered_down.png   |  Bin 0 -> 468 bytes
 .../internal/resource/xp/bartab_hovered_left.png   |  Bin 0 -> 456 bytes
 .../internal/resource/xp/bartab_hovered_right.png  |  Bin 0 -> 454 bytes
 .../internal/resource/xp/bartab_hovered_up.png     |  Bin 0 -> 464 bytes
 .../internal/resource/xp/bartab_normal_down.png    |  Bin 0 -> 473 bytes
 .../internal/resource/xp/bartab_normal_left.png    |  Bin 0 -> 478 bytes
 .../internal/resource/xp/bartab_normal_right.png   |  Bin 0 -> 479 bytes
 .../internal/resource/xp/bartab_normal_up.png      |  Bin 0 -> 483 bytes
 .../internal/resource/xp/button_close_hovered.png  |  Bin 0 -> 531 bytes
 .../internal/resource/xp/button_close_normal.png   |  Bin 0 -> 572 bytes
 .../internal/resource/xp/button_close_pressed.png  |  Bin 0 -> 575 bytes
 .../internal/resource/xp/button_dock_hovered.png   |  Bin 0 -> 497 bytes
 .../internal/resource/xp/button_dock_normal.png    |  Bin 0 -> 503 bytes
 .../internal/resource/xp/button_dock_pressed.png   |  Bin 0 -> 520 bytes
 .../resource/xp/button_maximize_hovered.png        |  Bin 0 -> 461 bytes
 .../resource/xp/button_maximize_normal.png         |  Bin 0 -> 475 bytes
 .../resource/xp/button_maximize_pressed.png        |  Bin 0 -> 487 bytes
 .../resource/xp/button_minimize_hovered.png        |  Bin 0 -> 450 bytes
 .../resource/xp/button_minimize_normal.png         |  Bin 0 -> 466 bytes
 .../resource/xp/button_minimize_pressed.png        |  Bin 0 -> 482 bytes
 .../resource/xp/button_restore_hovered.png         |  Bin 0 -> 485 bytes
 .../internal/resource/xp/button_restore_normal.png |  Bin 0 -> 502 bytes
 .../resource/xp/button_restore_pressed.png         |  Bin 0 -> 518 bytes
 .../internal/resource/xp/button_tab_close.png      |  Bin 0 -> 209 bytes
 .../theme/internal/resource/xp/button_tab_dock.png |  Bin 0 -> 210 bytes
 .../internal/resource/xp/button_tab_minimize.png   |  Bin 0 -> 183 bytes
 .../internal/resource/xp/button_tab_restore.png    |  Bin 0 -> 220 bytes
 .../internal/resource/xp/button_tab_undock.png     |  Bin 0 -> 208 bytes
 .../internal/resource/xp/button_undock_hovered.png |  Bin 0 -> 494 bytes
 .../internal/resource/xp/button_undock_normal.png  |  Bin 0 -> 508 bytes
 .../internal/resource/xp/button_undock_pressed.png |  Bin 0 -> 518 bytes
 .../resource/xp/tab_selected_nofocus_down.png      |  Bin 0 -> 270 bytes
 .../resource/xp/tab_selected_nofocus_left.png      |  Bin 0 -> 277 bytes
 .../resource/xp/tab_selected_nofocus_right.png     |  Bin 0 -> 280 bytes
 .../resource/xp/tab_selected_nofocus_up.png        |  Bin 0 -> 282 bytes
 src/net/infonode/docking/theme/package.html        |    5 +
 .../docking/title/DockingWindowTitleProvider.java  |   43 +
 .../title/DockingWindowTitleProviderProperty.java  |   72 +
 .../LengthLimitedDockingWindowTitleProvider.java   |  114 ++
 .../title/SimpleDockingWindowTitleProvider.java    |   76 +
 src/net/infonode/docking/title/package.html        |    5 +
 src/net/infonode/docking/util/AbstractViewMap.java |  146 ++
 src/net/infonode/docking/util/DeveloperUtil.java   |  208 ++
 src/net/infonode/docking/util/DockingUtil.java     |  198 ++
 .../infonode/docking/util/MixedViewHandler.java    |   76 +
 src/net/infonode/docking/util/PropertiesUtil.java  |  145 ++
 src/net/infonode/docking/util/StringViewMap.java   |  104 +
 src/net/infonode/docking/util/ViewFactory.java     |   57 +
 .../infonode/docking/util/ViewFactoryManager.java  |   39 +
 src/net/infonode/docking/util/ViewMap.java         |   95 +
 src/net/infonode/docking/util/WindowMenuUtil.java  |  297 +++
 src/net/infonode/docking/util/package.html         |    5 +
 src/net/infonode/gui/BackgroundPainter.java        |   42 +
 src/net/infonode/gui/ButtonFactory.java            |  282 +++
 src/net/infonode/gui/Colors.java                   |   47 +
 src/net/infonode/gui/ComponentPaintChecker.java    |   57 +
 src/net/infonode/gui/ComponentUtil.java            |  323 +++
 src/net/infonode/gui/ContainerList.java            |   51 +
 src/net/infonode/gui/ContentTitleBar.java          |  318 +++
 src/net/infonode/gui/CursorManager.java            |  161 ++
 src/net/infonode/gui/DimensionProvider.java        |   42 +
 src/net/infonode/gui/DimensionUtil.java            |   67 +
 src/net/infonode/gui/DragLabelWindow.java          |   78 +
 src/net/infonode/gui/DynamicUIManager.java         |  182 ++
 src/net/infonode/gui/DynamicUIManagerListener.java |   34 +
 src/net/infonode/gui/EventUtil.java                |   55 +
 src/net/infonode/gui/FlatIconButtonUI.java         |   62 +
 src/net/infonode/gui/FontUtil.java                 |   35 +
 src/net/infonode/gui/GraphicsUtil.java             |   46 +
 src/net/infonode/gui/HighlightPainter.java         |   71 +
 src/net/infonode/gui/InsetsUtil.java               |  131 ++
 src/net/infonode/gui/PopupList.java                |  233 +++
 src/net/infonode/gui/PopupListListener.java        |   31 +
 src/net/infonode/gui/RectangleUtil.java            |   61 +
 src/net/infonode/gui/ReleaseInfoDialog.java        |   89 +
 src/net/infonode/gui/RotatableLabel.java           |  109 +
 src/net/infonode/gui/RotatableLabelUI.java         |  127 ++
 src/net/infonode/gui/ScrollButtonBox.java          |  255 +++
 src/net/infonode/gui/ScrollButtonBoxListener.java  |   30 +
 src/net/infonode/gui/ScrollableBox.java            |  286 +++
 src/net/infonode/gui/ScrollableBoxListener.java    |   35 +
 src/net/infonode/gui/SimpleSplitPane.java          |  378 ++++
 src/net/infonode/gui/SimpleSplitPaneListener.java  |   32 +
 src/net/infonode/gui/TextIconListCellRenderer.java |   88 +
 src/net/infonode/gui/TranslatingShape.java         |  132 ++
 src/net/infonode/gui/UIManagerUtil.java            |  113 ++
 src/net/infonode/gui/action/SimpleAction.java      |   62 +
 src/net/infonode/gui/action/package.html           |    5 +
 src/net/infonode/gui/border/BorderUtil.java        |   77 +
 src/net/infonode/gui/border/EdgeBorder.java        |  132 ++
 src/net/infonode/gui/border/EtchedLineBorder.java  |  119 ++
 src/net/infonode/gui/border/FocusBorder.java       |   95 +
 src/net/infonode/gui/border/HighlightBorder.java   |   95 +
 src/net/infonode/gui/border/PopupMenuBorder.java   |   66 +
 src/net/infonode/gui/button/ButtonFactory.java     |   43 +
 src/net/infonode/gui/button/FlatButtonFactory.java |   41 +
 src/net/infonode/gui/button/package.html           |    5 +
 .../gui/colorprovider/AbstractColorProvider.java   |   42 +
 .../gui/colorprovider/BackgroundColorProvider.java |   58 +
 .../BackgroundPainterColorProvider.java            |   63 +
 .../infonode/gui/colorprovider/ColorBlender.java   |   65 +
 .../gui/colorprovider/ColorMultiplier.java         |   61 +
 .../infonode/gui/colorprovider/ColorProvider.java  |   52 +
 .../gui/colorprovider/ColorProviderList.java       |   72 +
 .../gui/colorprovider/ColorProviderUtil.java       |   49 +
 .../gui/colorprovider/FixedColorProvider.java      |   67 +
 .../gui/colorprovider/UIManagerColorProvider.java  |  108 +
 src/net/infonode/gui/colorprovider/package.html    |    5 +
 .../componentpainter/AbstractComponentPainter.java |   87 +
 .../AbstractComponentPainterWrapper.java           |   66 +
 .../gui/componentpainter/ComponentPainter.java     |   90 +
 .../componentpainter/CompoundComponentPainter.java |   68 +
 .../FixedTransformComponentPainter.java            |   78 +
 .../componentpainter/GradientComponentPainter.java |  252 +++
 .../RectangleComponentPainter.java                 |  109 +
 .../SolidColorComponentPainter.java                |   78 +
 src/net/infonode/gui/componentpainter/package.html |    5 +
 .../infonode/gui/draggable/DraggableComponent.java |  557 ++++++
 .../gui/draggable/DraggableComponentAdapter.java   |   41 +
 .../gui/draggable/DraggableComponentBox.java       |  732 +++++++
 .../draggable/DraggableComponentBoxAdapter.java    |   47 +
 .../gui/draggable/DraggableComponentBoxEvent.java  |   85 +
 .../draggable/DraggableComponentBoxListener.java   |   40 +
 .../gui/draggable/DraggableComponentEvent.java     |   70 +
 .../gui/draggable/DraggableComponentListener.java  |   36 +
 .../infonode/gui/hover/CompoundHoverListener.java  |   78 +
 src/net/infonode/gui/hover/HoverEvent.java         |   56 +
 src/net/infonode/gui/hover/HoverListener.java      |   48 +
 .../gui/hover/action/DelayedHoverExitAction.java   |   96 +
 src/net/infonode/gui/hover/action/package.html     |    5 +
 .../infonode/gui/hover/hoverable/HoverManager.java |  290 +++
 .../infonode/gui/hover/hoverable/Hoverable.java    |   37 +
 src/net/infonode/gui/hover/package.html            |    5 +
 .../gui/hover/panel/HoverableShapedPanel.java      |  110 ++
 src/net/infonode/gui/icon/EmptyIcon.java           |   49 +
 src/net/infonode/gui/icon/IconProvider.java        |   41 +
 src/net/infonode/gui/icon/IconUtil.java            |   97 +
 .../gui/icon/button/AbstractButtonIcon.java        |  117 ++
 src/net/infonode/gui/icon/button/ArrowIcon.java    |   74 +
 src/net/infonode/gui/icon/button/BorderIcon.java   |   68 +
 src/net/infonode/gui/icon/button/CloseIcon.java    |   61 +
 src/net/infonode/gui/icon/button/DockIcon.java     |   69 +
 src/net/infonode/gui/icon/button/DropDownIcon.java |   49 +
 src/net/infonode/gui/icon/button/MaximizeIcon.java |   60 +
 src/net/infonode/gui/icon/button/MinimizeIcon.java |   57 +
 src/net/infonode/gui/icon/button/RestoreIcon.java  |   69 +
 src/net/infonode/gui/icon/button/TreeIcon.java     |   85 +
 src/net/infonode/gui/icon/button/UndockIcon.java   |   69 +
 src/net/infonode/gui/icon/button/WindowIcon.java   |   56 +
 src/net/infonode/gui/icon/package.html             |    5 +
 src/net/infonode/gui/laf/InfoNodeLookAndFeel.java  |  453 +++++
 .../gui/laf/InfoNodeLookAndFeelReleaseInfo.java    |   53 +
 .../infonode/gui/laf/InfoNodeLookAndFeelTheme.java | 1223 ++++++++++++
 .../gui/laf/InfoNodeLookAndFeelThemes.java         |  127 ++
 src/net/infonode/gui/laf/info/Info.java            |   38 +
 src/net/infonode/gui/laf/package.html              |    5 +
 src/net/infonode/gui/laf/ui/SlimComboBoxUI.java    |   60 +
 .../gui/laf/ui/SlimInternalFrameTitlePane.java     |   62 +
 .../infonode/gui/laf/ui/SlimInternalFrameUI.java   |   47 +
 src/net/infonode/gui/laf/ui/SlimMenuItemUI.java    |   44 +
 .../infonode/gui/laf/ui/SlimSplitPaneDivider.java  |   64 +
 src/net/infonode/gui/laf/ui/SlimSplitPaneUI.java   |   44 +
 src/net/infonode/gui/laf/value/BorderValue.java    |   63 +
 src/net/infonode/gui/laf/value/ColorValue.java     |   76 +
 src/net/infonode/gui/laf/value/FontValue.java      |   62 +
 src/net/infonode/gui/layout/BorderLayout2.java     |  254 +++
 src/net/infonode/gui/layout/DirectionLayout.java   |  347 ++++
 src/net/infonode/gui/layout/LayoutUtil.java        |  181 ++
 src/net/infonode/gui/layout/StackableLayout.java   |  160 ++
 src/net/infonode/gui/layout/StretchLayout.java     |   71 +
 src/net/infonode/gui/menu/MenuUtil.java            |   89 +
 .../infonode/gui/mouse/MouseButtonListener.java    |   42 +
 src/net/infonode/gui/mouse/package.html            |    5 +
 src/net/infonode/gui/panel/BaseContainer.java      |  174 ++
 src/net/infonode/gui/panel/BaseContainerUtil.java  |   45 +
 src/net/infonode/gui/panel/BasePanel.java          |   59 +
 src/net/infonode/gui/panel/DirectionPanel.java     |   67 +
 src/net/infonode/gui/panel/ResizablePanel.java     |  271 +++
 src/net/infonode/gui/panel/SimplePanel.java        |   82 +
 src/net/infonode/gui/panel/package.html            |    5 +
 src/net/infonode/gui/shaped/ShapedUtil.java        |  109 +
 .../gui/shaped/border/AbstractPolygonBorder.java   |  247 +++
 .../gui/shaped/border/AbstractShapedBorder.java    |   35 +
 .../shaped/border/AbstractShapedBorderWrapper.java |   56 +
 .../gui/shaped/border/FixedInsetsShapedBorder.java |   48 +
 .../infonode/gui/shaped/border/PolygonBorder.java  |   87 +
 .../gui/shaped/border/RoundedCornerBorder.java     |  197 ++
 .../infonode/gui/shaped/border/ShapedBorder.java   |   49 +
 src/net/infonode/gui/shaped/border/package.html    |    5 +
 src/net/infonode/gui/shaped/panel/ShapedPanel.java |  244 +++
 src/net/infonode/properties/base/Property.java     |  128 ++
 .../infonode/properties/base/PropertyGroup.java    |  173 ++
 .../base/exception/CantRemoveValueException.java   |   44 +
 .../base/exception/ImmutablePropertyException.java |   44 +
 .../base/exception/InvalidPropertyException.java   |   45 +
 .../exception/InvalidPropertyTypeException.java    |   58 +
 .../exception/InvalidPropertyValueException.java   |   56 +
 .../base/exception/PropertyException.java          |   56 +
 .../properties/base/exception/package.html         |    5 +
 src/net/infonode/properties/base/package.html      |    5 +
 .../properties/gui/InternalPropertiesUtil.java     |   62 +
 .../properties/gui/util/ButtonProperties.java      |  252 +++
 .../properties/gui/util/ComponentProperties.java   |  290 +++
 .../properties/gui/util/ShapedPanelProperties.java |  305 +++
 src/net/infonode/properties/gui/util/package.html  |    5 +
 .../properties/propertymap/PropertyMap.java        |  260 +++
 .../propertymap/PropertyMapContainer.java          |   61 +
 .../properties/propertymap/PropertyMapFactory.java |   56 +
 .../properties/propertymap/PropertyMapGroup.java   |   71 +
 .../properties/propertymap/PropertyMapImpl.java    |  923 +++++++++
 .../propertymap/PropertyMapListener.java           |   43 +
 .../properties/propertymap/PropertyMapManager.java |  158 ++
 .../propertymap/PropertyMapProperty.java           |   75 +
 .../propertymap/PropertyMapTreeListener.java       |   44 +
 .../properties/propertymap/PropertyMapUtil.java    |   50 +
 .../propertymap/PropertyMapValueHandler.java       |   75 +
 .../PropertyMapWeakListenerManager.java            |  288 +++
 .../infonode/properties/propertymap/package.html   |    6 +
 .../propertymap/ref/CompositeMapRef.java           |   62 +
 .../properties/propertymap/ref/ParentMapRef.java   |   53 +
 .../propertymap/ref/PropertyMapPropertyRef.java    |   65 +
 .../properties/propertymap/ref/PropertyMapRef.java |   39 +
 .../propertymap/ref/PropertyMapRefDecoder.java     |   62 +
 .../propertymap/ref/ThisPropertyMapRef.java        |   52 +
 .../propertymap/value/PropertyRefValue.java        |  165 ++
 .../propertymap/value/PropertyValue.java           |   56 +
 .../propertymap/value/SimplePropertyValue.java     |  117 ++
 .../properties/propertymap/value/ValueDecoder.java |   74 +
 .../properties/types/AlignmentProperty.java        |   73 +
 .../infonode/properties/types/BooleanProperty.java |   70 +
 .../infonode/properties/types/BorderProperty.java  |   70 +
 .../properties/types/ButtonFactoryProperty.java    |   72 +
 .../infonode/properties/types/ColorProperty.java   |   71 +
 .../properties/types/ComponentPainterProperty.java |   65 +
 .../types/DimensionProviderProperty.java           |   60 +
 .../properties/types/DirectionProperty.java        |   68 +
 .../infonode/properties/types/EnumProperty.java    |   80 +
 .../infonode/properties/types/FloatProperty.java   |  165 ++
 .../infonode/properties/types/FontProperty.java    |   71 +
 .../properties/types/HoverListenerProperty.java    |   72 +
 .../infonode/properties/types/IconProperty.java    |   71 +
 .../infonode/properties/types/InsetsProperty.java  |   63 +
 .../infonode/properties/types/IntegerProperty.java |  100 +
 .../infonode/properties/types/NumberProperty.java  |  107 +
 .../properties/types/PropertyGroupProperty.java    |   65 +
 .../infonode/properties/types/StringProperty.java  |   69 +
 src/net/infonode/properties/types/package.html     |    5 +
 .../infonode/properties/util/AbstractProperty.java |   93 +
 .../properties/util/PropertyChangeListener.java    |   44 +
 src/net/infonode/properties/util/PropertyPath.java |   86 +
 .../properties/util/PropertyValueHandler.java      |   78 +
 .../properties/util/ValueHandlerProperty.java      |   75 +
 src/net/infonode/properties/util/package.html      |    5 +
 src/net/infonode/tabbedpanel/Tab.java              |  548 ++++++
 src/net/infonode/tabbedpanel/TabAdapter.java       |   65 +
 .../tabbedpanel/TabAreaComponentsProperties.java   |  223 +++
 .../infonode/tabbedpanel/TabAreaProperties.java    |  221 +++
 .../infonode/tabbedpanel/TabAreaVisiblePolicy.java |   75 +
 .../tabbedpanel/TabAreaVisiblePolicyProperty.java  |   78 +
 src/net/infonode/tabbedpanel/TabContentPanel.java  |  124 ++
 .../infonode/tabbedpanel/TabDepthOrderPolicy.java  |   67 +
 .../tabbedpanel/TabDepthOrderPolicyProperty.java   |   79 +
 src/net/infonode/tabbedpanel/TabDragEvent.java     |   91 +
 .../tabbedpanel/TabDropDownListVisiblePolicy.java  |   76 +
 .../TabDropDownListVisiblePolicyProperty.java      |   78 +
 src/net/infonode/tabbedpanel/TabEvent.java         |   63 +
 src/net/infonode/tabbedpanel/TabFactory.java       |   99 +
 src/net/infonode/tabbedpanel/TabLayoutPolicy.java  |   71 +
 .../tabbedpanel/TabLayoutPolicyProperty.java       |   72 +
 src/net/infonode/tabbedpanel/TabListener.java      |  115 ++
 src/net/infonode/tabbedpanel/TabRemovedEvent.java  |   59 +
 src/net/infonode/tabbedpanel/TabSelectTrigger.java |   67 +
 .../tabbedpanel/TabSelectTriggerProperty.java      |   75 +
 .../infonode/tabbedpanel/TabStateChangedEvent.java |   91 +
 src/net/infonode/tabbedpanel/TabbedPanel.java      | 1625 +++++++++++++++
 .../tabbedpanel/TabbedPanelButtonProperties.java   |  189 ++
 .../tabbedpanel/TabbedPanelContentPanel.java       |  237 +++
 .../TabbedPanelContentPanelProperties.java         |  190 ++
 .../TabbedPanelDefaultButtonFactories.java         |   84 +
 .../tabbedpanel/TabbedPanelHoverPolicy.java        |   90 +
 .../TabbedPanelHoverPolicyProperty.java            |   79 +
 .../tabbedpanel/TabbedPanelProperties.java         | 1253 ++++++++++++
 .../tabbedpanel/TabbedPanelReleaseInfo.java        |   56 +
 src/net/infonode/tabbedpanel/TabbedUIDefaults.java |  170 ++
 src/net/infonode/tabbedpanel/TabbedUtils.java      |  110 ++
 .../tabbedpanel/border/GradientTabAreaBorder.java  |  109 +
 .../tabbedpanel/border/OpenContentBorder.java      |  341 ++++
 .../tabbedpanel/border/TabAreaLineBorder.java      |  196 ++
 .../tabbedpanel/border/TabHighlightBorder.java     |  127 ++
 .../infonode/tabbedpanel/border/TabLineBorder.java |  347 ++++
 src/net/infonode/tabbedpanel/border/package.html   |    5 +
 .../tabbedpanel/hover/TabbedPanelHoverAction.java  |   91 +
 .../hover/TabbedPanelTitledTabHoverAction.java     |  225 +++
 .../TitledTabDelayedMouseExitHoverAction.java      |  119 ++
 .../tabbedpanel/hover/TitledTabHoverAction.java    |   88 +
 .../hover/TitledTabTabbedPanelHoverAction.java     |  201 ++
 src/net/infonode/tabbedpanel/hover/package.html    |    5 +
 src/net/infonode/tabbedpanel/info/Info.java        |   48 +
 .../tabbedpanel/internal/ShadowPainter.java        |  449 +++++
 .../tabbedpanel/internal/SlopedTabLineBorder.java  |  239 +++
 .../tabbedpanel/internal/TabDropDownList.java      |  106 +
 .../tabbedpanel/internal/TabbedHoverUtil.java      |  106 +
 .../tabbedpanel/internal/TwoColoredLineBorder.java |  207 ++
 src/net/infonode/tabbedpanel/package.html          |    5 +
 .../tabbedpanel/theme/BlueHighlightTheme.java      |   98 +
 .../infonode/tabbedpanel/theme/ClassicTheme.java   |  253 +++
 .../infonode/tabbedpanel/theme/DefaultTheme.java   |   76 +
 .../infonode/tabbedpanel/theme/GradientTheme.java  |  227 +++
 .../tabbedpanel/theme/LookAndFeelTheme.java        |  321 +++
 .../tabbedpanel/theme/ShapedGradientTheme.java     |  399 ++++
 .../infonode/tabbedpanel/theme/SmallFlatTheme.java |   94 +
 .../tabbedpanel/theme/SoftBlueIceTheme.java        |  213 ++
 .../theme/TabbedPanelTitledTabTheme.java           |   57 +
 .../theme/internal/laftheme/ComponentCache.java    |   71 +
 .../theme/internal/laftheme/PaneHandler.java       |  116 ++
 .../internal/laftheme/PaneHandlerListener.java     |   30 +
 .../theme/internal/laftheme/PanePainter.java       |  190 ++
 .../theme/internal/laftheme/PaneUI.java            | 1201 +++++++++++
 .../theme/internal/laftheme/PaneUIListener.java    |   30 +
 .../theme/internal/laftheme/SizeIcon.java          |   56 +
 .../theme/internal/laftheme/TabData.java           |  152 ++
 .../internal/resource/xp/button_down_hovered.png   |  Bin 0 -> 505 bytes
 .../internal/resource/xp/button_down_normal.png    |  Bin 0 -> 519 bytes
 .../internal/resource/xp/button_down_pressed.png   |  Bin 0 -> 539 bytes
 .../resource/xp/button_dropdown_hovered.png        |  Bin 0 -> 474 bytes
 .../resource/xp/button_dropdown_normal.png         |  Bin 0 -> 487 bytes
 .../resource/xp/button_dropdown_pressed.png        |  Bin 0 -> 497 bytes
 .../internal/resource/xp/button_left_hovered.png   |  Bin 0 -> 499 bytes
 .../internal/resource/xp/button_left_normal.png    |  Bin 0 -> 517 bytes
 .../internal/resource/xp/button_left_pressed.png   |  Bin 0 -> 539 bytes
 .../internal/resource/xp/button_right_hovered.png  |  Bin 0 -> 507 bytes
 .../internal/resource/xp/button_right_normal.png   |  Bin 0 -> 509 bytes
 .../internal/resource/xp/button_right_pressed.png  |  Bin 0 -> 543 bytes
 .../internal/resource/xp/button_up_hovered.png     |  Bin 0 -> 498 bytes
 .../internal/resource/xp/button_up_normal.png      |  Bin 0 -> 519 bytes
 .../internal/resource/xp/button_up_pressed.png     |  Bin 0 -> 540 bytes
 .../internal/resource/xp/tab_hovered_down.png      |  Bin 0 -> 394 bytes
 .../internal/resource/xp/tab_hovered_left.png      |  Bin 0 -> 372 bytes
 .../internal/resource/xp/tab_hovered_right.png     |  Bin 0 -> 395 bytes
 .../theme/internal/resource/xp/tab_hovered_up.png  |  Bin 0 -> 385 bytes
 .../theme/internal/resource/xp/tab_normal_down.png |  Bin 0 -> 401 bytes
 .../theme/internal/resource/xp/tab_normal_left.png |  Bin 0 -> 389 bytes
 .../internal/resource/xp/tab_normal_right.png      |  Bin 0 -> 407 bytes
 .../theme/internal/resource/xp/tab_normal_up.png   |  Bin 0 -> 398 bytes
 .../internal/resource/xp/tab_selected_down.png     |  Bin 0 -> 249 bytes
 .../internal/resource/xp/tab_selected_left.png     |  Bin 0 -> 245 bytes
 .../internal/resource/xp/tab_selected_right.png    |  Bin 0 -> 267 bytes
 .../theme/internal/resource/xp/tab_selected_up.png |  Bin 0 -> 247 bytes
 src/net/infonode/tabbedpanel/theme/package.html    |    5 +
 .../infonode/tabbedpanel/titledtab/TitledTab.java  | 1157 +++++++++++
 .../titledtab/TitledTabBorderSizePolicy.java       |   80 +
 .../TitledTabBorderSizePolicyProperty.java         |   77 +
 .../tabbedpanel/titledtab/TitledTabProperties.java |  618 ++++++
 .../tabbedpanel/titledtab/TitledTabSizePolicy.java |   71 +
 .../titledtab/TitledTabSizePolicyProperty.java     |   72 +
 .../titledtab/TitledTabStateProperties.java        |  602 ++++++
 .../infonode/tabbedpanel/titledtab/package.html    |    5 +
 src/net/infonode/util/Alignment.java               |  122 ++
 src/net/infonode/util/AntUtils.java                |   48 +
 src/net/infonode/util/ArrayUtil.java               |  281 +++
 src/net/infonode/util/ChangeNotifyList.java        |  227 +++
 src/net/infonode/util/ColorUtil.java               |   82 +
 src/net/infonode/util/Direction.java               |  135 ++
 src/net/infonode/util/Enum.java                    |  117 ++
 src/net/infonode/util/ImageException.java          |   30 +
 src/net/infonode/util/ImageUtils.java              |  171 ++
 src/net/infonode/util/IntList.java                 |  105 +
 src/net/infonode/util/Printer.java                 |   77 +
 src/net/infonode/util/ProductVersion.java          |   92 +
 src/net/infonode/util/ReadWritable.java            |   33 +
 src/net/infonode/util/Readable.java                |   43 +
 src/net/infonode/util/ReleaseInfo.java             |  130 ++
 src/net/infonode/util/StreamUtil.java              |   94 +
 src/net/infonode/util/Utils.java                   |   38 +
 src/net/infonode/util/ValueChange.java             |   64 +
 src/net/infonode/util/Writable.java                |   43 +
 src/net/infonode/util/collection/Closure.java      |   32 +
 src/net/infonode/util/collection/Collection.java   |   31 +
 .../infonode/util/collection/ConstCollection.java  |   34 +
 .../util/collection/CopyOnWriteArrayList.java      |  166 ++
 .../infonode/util/collection/EmptyIterator.java    |   49 +
 .../util/collection/map/ConstVectorMap.java        |  157 ++
 .../util/collection/map/EmptyIterator.java         |   51 +
 .../infonode/util/collection/map/MapAdapter.java   |  115 ++
 .../util/collection/map/SingleValueMap.java        |   81 +
 .../util/collection/map/base/ConstMap.java         |   65 +
 .../util/collection/map/base/ConstMapIterator.java |   69 +
 src/net/infonode/util/collection/map/base/Map.java |   59 +
 .../util/collection/map/base/MapIterator.java      |   29 +
 .../notifymap/AbstractChangeNotifyMap.java         |   33 +
 .../notifymap/AbstractConstChangeNotifyMap.java    |   87 +
 .../util/collection/notifymap/ChangeNotifyMap.java |   29 +
 .../notifymap/ChangeNotifyMapWrapper.java          |  114 ++
 .../collection/notifymap/ConstChangeNotifyMap.java |   31 +
 .../notifymap/ConstChangeNotifyVectorMap.java      |  186 ++
 src/net/infonode/util/math/Int4.java               |  127 ++
 src/net/infonode/util/package.html                 |    5 +
 src/net/infonode/util/signal/Signal.java           |  198 ++
 src/net/infonode/util/signal/SignalHook.java       |   36 +
 src/net/infonode/util/signal/SignalListener.java   |   32 +
 539 files changed, 62333 insertions(+)

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..45645b4
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+

+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+

+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+

+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+

+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+

+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/src/net/infonode/docking/AbstractTabWindow.java b/src/net/infonode/docking/AbstractTabWindow.java
new file mode 100644
index 0000000..8ab74c8
--- /dev/null
+++ b/src/net/infonode/docking/AbstractTabWindow.java
@@ -0,0 +1,616 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractTabWindow.java,v 1.74 2009/02/05 15:57:55 jesper Exp $
+package net.infonode.docking;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+
+import net.infonode.docking.drop.InsertTabDropInfo;
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.WindowAncestors;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.internalutil.DropAction;
+import net.infonode.docking.model.AbstractTabWindowItem;
+import net.infonode.docking.model.ViewReader;
+import net.infonode.docking.model.ViewWriter;
+import net.infonode.docking.model.WindowItem;
+import net.infonode.docking.properties.TabWindowProperties;
+import net.infonode.docking.properties.WindowTabProperties;
+import net.infonode.properties.propertymap.PropertyMapManager;
+import net.infonode.tabbedpanel.*;
+import net.infonode.util.ChangeNotifyList;
+
+/**
+ * Abstract base class for windows containing a tabbed panel.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.74 $
+ */
+abstract public class AbstractTabWindow extends DockingWindow {
+  private static int MINIMUM_SIZE = 7;
+
+  private final DropAction dropAction = new DropAction() {
+    public boolean showTitle() {
+      return false;
+    }
+
+    public void execute(DockingWindow window, MouseEvent mouseEvent) {
+      if (window.getWindowParent() == AbstractTabWindow.this) {
+        // Tab moved inside window
+        updateWindowItem(window);
+      }
+      else {
+        int index = tabbedPanel.getTabIndex(dragTab);
+        stopDrag();
+
+        try {
+          window.beforeDrop(AbstractTabWindow.this);
+
+          if (mouseEvent.isShiftDown())
+            addTabNoSelect(window, index);
+          else
+            addTab(window, index);
+        }
+        catch (OperationAbortedException e) {
+          // Ignore
+        }
+      }
+    }
+
+    public void clear(DockingWindow window, DropAction newDropAction) {
+      if (newDropAction != this) {
+        if (window.getWindowParent() == AbstractTabWindow.this) {
+          WindowTab tab = window.getTab();
+          boolean selected = tab.isSelected();
+          tabbedPanel.removeTab(tab);
+          tabbedPanel.insertTab(tab, draggedTabIndex);
+
+          if (selected)
+            tab.setSelected(true);
+        }
+        else {
+          stopDrag();
+        }
+      }
+    }
+  };
+
+  private TabbedPanel tabbedPanel;
+
+  /**
+   * Temporary drag tab.
+   */
+  private WindowTab dragTab;
+
+  private int ignoreSelected;
+
+  private int draggedTabIndex;
+
+  private java.util.List tabAreaComponents;
+
+  /**
+   * Returns the properties for this tab window.
+   *
+   * @return the properties for this tab window
+   */
+  abstract public TabWindowProperties getTabWindowProperties();
+
+  protected AbstractTabWindow(boolean showContent, WindowItem windowItem) {
+    super(windowItem);
+    if (showContent) {
+      TabContentPanel contentPanel = new TabContentPanel() {
+        public Dimension getMinimumSize() {
+          if (getTabWindowProperties().getRespectChildWindowMinimumSize())
+            return super.getMinimumSize();
+
+          return new Dimension(0, 0);
+        }
+      };
+      tabbedPanel = new TabbedPanel(contentPanel, true) {
+        public Dimension getMinimumSize() {
+          if (getTabWindowProperties().getRespectChildWindowMinimumSize())
+            return super.getMinimumSize();
+
+          return getTabbedPanelMinimumSize(super.getMinimumSize());
+        }
+      };
+      contentPanel.setTabbedPanel(tabbedPanel);
+    }
+    else
+      tabbedPanel = new TabbedPanel(null) {
+      public Dimension getMinimumSize() {
+        if (getTabWindowProperties().getRespectChildWindowMinimumSize())
+          return super.getMinimumSize();
+
+        return getTabbedPanelMinimumSize(super.getMinimumSize());
+      }
+    };
+
+    tabbedPanel.addTabListener(new TabWindowMover(this, tabbedPanel));
+    setComponent(tabbedPanel);
+
+    getTabbedPanel().addTabListener(new TabAdapter() {
+      public void tabAdded(final TabEvent event) {
+        SwingUtilities.invokeLater(new Runnable() {
+          public void run() {
+            updateButtonVisibility();
+          }
+        });
+      }
+
+      public void tabRemoved(final TabRemovedEvent event) {
+        SwingUtilities.invokeLater(new Runnable() {
+          public void run() {
+            updateButtonVisibility();
+          }
+        });
+      }
+
+      public void tabSelected(TabStateChangedEvent event) {
+        AbstractTabWindow.this.tabSelected((WindowTab) event.getTab());
+        DockingWindow selectedWindow = getSelectedWindow();
+
+        if (!getIgnoreSelected() && selectedWindow != null)
+          selectedWindow.fireWindowShown(selectedWindow);
+      }
+
+      public void tabDeselected(TabStateChangedEvent event) {
+        WindowTab tab = (WindowTab) event.getTab();
+
+        if (tab != null && !getIgnoreSelected())
+          tab.getWindow().fireWindowHidden(tab.getWindow());
+      }
+
+      public void tabMoved(TabEvent event) {
+        if (!getIgnoreSelected())
+          fireTitleChanged();
+      }
+    });
+  }
+
+  protected void initMouseListener() {
+    getTabbedPanel().addMouseListener(new MouseAdapter() {
+      public void mousePressed(MouseEvent e) {
+        if (e.isPopupTrigger()) {
+          showPopupMenu(e);
+        }
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        mousePressed(e);
+      }
+    });
+  }
+
+  private Dimension getTabbedPanelMinimumSize(Dimension d) {
+    if (tabbedPanel.getProperties().getTabAreaOrientation().isHorizontal())
+      return new Dimension(d.width, MINIMUM_SIZE);
+    else
+      return new Dimension(MINIMUM_SIZE, d.height);
+  }
+
+  /**
+   * <p>
+   * Returns a list containing the custom tab area components. Changes to the
+   * list will be propagated to the tab area.
+   * </p>
+   * <p>
+   * The custom tab area components will between the scroll buttons and the
+   * window buttons in the tab area components panel. The components are shown
+   * in the same order as they appear in the list. The tab area components
+   * container layout is rotated with the tab window tab orientation.
+   * </p>
+   *
+   * @return a list containing the custom tab area components, list elements are
+   *         of type {@link JComponent}
+   * @since IDW 1.3.0
+   */
+  public final java.util.List getCustomTabAreaComponents() {
+    if (tabAreaComponents == null)
+      tabAreaComponents = new ChangeNotifyList() {
+      protected void changed() {
+        updateTabAreaComponents();
+      }
+    };
+
+    return tabAreaComponents;
+  }
+
+  /**
+   * Returns the currently selected window in the tabbed panel.
+   *
+   * @return the currently selected window in the tabbed panel
+   */
+  public DockingWindow getSelectedWindow() {
+    WindowTab tab = (WindowTab) tabbedPanel.getSelectedTab();
+    return tab == null ? null : tab.getWindow();
+  }
+
+  /**
+   * Selects the tab with the index.
+   *
+   * @param index the tab index
+   */
+  public void setSelectedTab(int index) {
+    beginIgnoreSelected();
+
+    try {
+      Tab tab = index == -1 ? null : tabbedPanel.getTabAt(index);
+      Tab oldTab = tabbedPanel.getSelectedTab();
+
+      if (tab != oldTab) {
+        tabbedPanel.setSelectedTab(tab);
+        fireTitleChanged();
+
+        if (oldTab != null)
+          ((WindowTab) oldTab).getWindow().fireWindowHidden(((WindowTab) oldTab).getWindow());
+
+        if (tab != null)
+          ((WindowTab) tab).getWindow().fireWindowShown(((WindowTab) tab).getWindow());
+      }
+    }
+    finally {
+      endIgnoreSelected();
+    }
+  }
+
+  /**
+   * Adds a window tab last in this tab window.
+   *
+   * @param window the window
+   */
+  public void addTab(DockingWindow window) {
+    PropertyMapManager.getInstance().beginBatch();
+
+    try {
+      addTab(window, tabbedPanel.getTabCount());
+    }
+    finally {
+      PropertyMapManager.getInstance().endBatch();
+    }
+  }
+
+  /**
+   * Inserts a window tab at an index in this tab window.
+   *
+   * @param window the window
+   * @param index  the index where to insert the tab
+   * @return the index of the added tab, this might not be the same as
+   *         <tt>index</tt> if the tab already is added to this tab window
+   */
+  public int addTab(DockingWindow window, int index) {
+    PropertyMapManager.getInstance().beginBatch();
+
+    try {
+      return addTabNoSelect(window, index);
+    }
+    finally {
+      PropertyMapManager.getInstance().endBatch();
+    }
+  }
+
+  protected int addTabNoSelect(DockingWindow window, int index) {
+    WindowAncestors ancestors = window.storeAncestors();
+    beginOptimize(window.getWindowParent());
+    beginIgnoreSelected();
+
+    try {
+      Tab beforeTab = index >= tabbedPanel.getTabCount() ? null : tabbedPanel.getTabAt(index);
+      DockingWindow w = window.getContentWindow(this);
+      w.detach();
+      updateTab(w);
+      WindowTab tab = w.getTab();
+      int actualIndex = beforeTab == null ? tabbedPanel.getTabCount() : tabbedPanel.getTabIndex(beforeTab);
+      tabbedPanel.insertTab(tab, actualIndex);
+      addWindow(w);
+      window.notifyListeners(ancestors);
+      return actualIndex;
+    }
+    finally {
+      endIgnoreSelected();
+      endOptimize();
+    }
+  }
+
+  protected boolean isChildShowingInRootWindow(DockingWindow child) {
+    return super.isChildShowingInRootWindow(child) && child == getSelectedWindow();
+  }
+
+  protected void showChildWindow(DockingWindow window) {
+    setSelectedTab(getChildWindowIndex(window));
+    super.showChildWindow(window);
+  }
+
+  protected boolean childInsideTab() {
+    return true;
+  }
+
+  protected void setTabWindowProperties(TabWindowProperties properties) {
+    getTabbedPanel().getProperties().addSuperObject(properties.getTabbedPanelProperties());
+  }
+
+  protected void clearFocus(View view) {
+    if (getSelectedWindow() != null) {
+      getSelectedWindow().clearFocus(view);
+    }
+  }
+
+  protected DockingWindow getPreferredFocusChild() {
+    return getSelectedWindow() == null ? super.getPreferredFocusChild() : getSelectedWindow();
+  }
+
+  protected void clearChildrenFocus(DockingWindow child, View view) {
+    if (getSelectedWindow() != child)
+      clearFocus(view);
+  }
+
+  protected int getTabAreaComponentCount() {
+    return 0;
+  }
+
+  protected void updateTabAreaComponents() {
+    int ls = tabAreaComponents == null ? 0 : tabAreaComponents.size();
+    JComponent[] components = new JComponent[ls + getTabAreaComponentCount()];
+
+    if (tabAreaComponents != null)
+      tabAreaComponents.toArray(components);
+
+    getTabAreaComponents(ls, components);
+    getTabbedPanel().setTabAreaComponents(components);
+  }
+
+  protected void getTabAreaComponents(int index, JComponent[] components) {
+  }
+
+  protected final boolean getIgnoreSelected() {
+    return ignoreSelected > 0;
+  }
+
+  protected void tabSelected(WindowTab tab) {
+    if (!getIgnoreSelected() && tab != null) {
+      final RootWindow root = getRootWindow();
+
+      if (root != null) {
+        // Anticipate the focus movement to avoid flicker
+        tab.setFocused(true);
+        root.addFocusedWindow(tab.getWindow());
+        FocusManager.focusWindow(tab.getWindow());
+      }
+    }
+
+    if (!getIgnoreSelected())
+      fireTitleChanged();
+  }
+
+  protected TabbedPanel getTabbedPanel() {
+    return tabbedPanel;
+  }
+
+  public DockingWindow getChildWindow(int index) {
+    return ((WindowTab) tabbedPanel.getTabAt(index)).getWindow();
+  }
+
+  protected DockingWindow getLocationWindow() {
+    return tabbedPanel.getTabCount() == 1 ? getChildWindow(0) : this;
+  }
+
+  public int getChildWindowCount() {
+    return tabbedPanel.getTabCount();
+  }
+
+  public Icon getIcon() {
+    DockingWindow window = getSelectedWindow();
+    return window != null ? window.getIcon() : getChildWindowCount() > 0 ? getChildWindow(0).getIcon() : null;
+  }
+
+  private void updateTab(DockingWindow window) {
+    window.getTab().setProperties(getTabProperties(window));
+  }
+
+  private WindowTabProperties getTabProperties(DockingWindow window) {
+    WindowTabProperties properties = new WindowTabProperties(getTabWindowProperties().getTabProperties());
+    properties.addSuperObject(window.getWindowProperties().getTabProperties());
+    return properties;
+  }
+
+  protected void doReplace(DockingWindow oldWindow, DockingWindow newWindow) {
+    beginIgnoreSelected();
+
+    try {
+      Tab tab = oldWindow.getTab();
+      int tabIndex = tabbedPanel.getTabIndex(tab);
+
+      boolean selected = tab.isSelected();
+      tabbedPanel.removeTab(tab);
+      tabbedPanel.insertTab(newWindow.getTab(), tabIndex);
+
+      if (selected)
+        tabbedPanel.setSelectedTab(newWindow.getTab());
+
+      updateTab(newWindow);
+    }
+    finally {
+      endIgnoreSelected();
+    }
+  }
+
+  protected void doRemoveWindow(DockingWindow window) {
+    beginIgnoreSelected();
+
+    try {
+      WindowTab tab = window.getTab();
+      tab.unsetProperties();
+      tabbedPanel.removeTab(tab);
+    }
+    finally {
+      endIgnoreSelected();
+    }
+  }
+
+  private void beginIgnoreSelected() {
+    ignoreSelected++;
+  }
+
+  private void endIgnoreSelected() {
+    ignoreSelected--;
+  }
+
+  protected boolean isInsideTabArea(Point p2) {
+    return tabbedPanel.tabAreaContainsPoint(p2);
+  }
+
+  protected DropAction acceptInteriorDrop(Point p, DockingWindow window) {
+    if (getChildWindowCount() == 1 && window == getChildWindow(0) && dragTab == null)
+      return null;
+
+    Point p2 = SwingUtilities.convertPoint(this, p, tabbedPanel);
+
+    if ((getRootWindow().getRootWindowProperties().getRecursiveTabsEnabled() || window.getChildWindowCount() <= 1) &&
+        isInsideTabArea(p2)) {
+      getRootWindow().setDragRectangle(null);
+
+      if (window.getWindowParent() == this) {
+        tabbedPanel.moveTab(window.getTab(), p2);
+      }
+      else {
+        if (!getInsertTabDropFilter().acceptDrop(new InsertTabDropInfo(window, this, p)))
+          return null;
+
+        if (dragTab == null) {
+          dragTab = createGhostTab(window);
+          tabbedPanel.insertTab(dragTab, p2);
+        }
+        else {
+          tabbedPanel.moveTab(dragTab, p2);
+        }
+      }
+
+      return dropAction;
+    }
+
+    return null;
+  }
+
+  WindowTab createGhostTab(DockingWindow window) {
+    WindowTab tab = new WindowTab(window, true);
+    tab.setProperties(getTabProperties(window));
+    return tab;
+  }
+
+  private void stopDrag() {
+    if (dragTab != null) {
+      tabbedPanel.removeTab(dragTab);
+      dragTab = null;
+    }
+  }
+
+  protected boolean showsWindowTitle() {
+    return true;
+  }
+
+  protected DockingWindow oldRead(ObjectInputStream in, ReadContext context) throws IOException {
+    int size = in.readInt();
+    int selectedIndex = in.readInt();
+
+    while (getChildWindowCount() > 0)
+      removeChildWindow(getChildWindow(0));
+
+    for (int i = 0; i < size; i++) {
+      DockingWindow window = WindowDecoder.decodeWindow(in, context);
+
+      if (window != null)
+        addTab(window);
+      else if (i < selectedIndex)
+        selectedIndex--;
+    }
+
+    super.oldRead(in, context);
+
+    if (tabbedPanel.getTabCount() > 0) {
+      if (selectedIndex >= 0)
+        setSelectedTab(Math.min(tabbedPanel.getTabCount() - 1, selectedIndex));
+
+      return this;
+    }
+    else
+      return null;
+  }
+
+  protected void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+    out.writeInt(getChildWindowCount());
+
+    for (int i = 0; i < getChildWindowCount(); i++)
+      getChildWindow(i).write(out, context, viewWriter);
+  }
+
+  protected DockingWindow newRead(ObjectInputStream in, ReadContext context, ViewReader viewReader) throws IOException {
+    int size = in.readInt();
+
+    while (getChildWindowCount() > 0)
+      removeChildWindow(getChildWindow(0));
+
+    for (int i = 0; i < size; i++) {
+      DockingWindow window = WindowDecoder.decodeWindow(in, context, viewReader);
+
+      if (window != null)
+        addTab(window);
+    }
+
+    updateSelectedTab();
+    return getChildWindowCount() > 0 ? this : null;
+  }
+
+  protected void updateSelectedTab() {
+    WindowItem selectedItem = ((AbstractTabWindowItem) getWindowItem()).getSelectedItem();
+
+    for (int i = 0; i < getChildWindowCount(); i++) {
+      if (getChildWindow(i).getWindowItem().hasAncestor(selectedItem)) {
+        setSelectedTab(i);
+        return;
+      }
+    }
+  }
+
+  void setDraggedTabIndex(int index) {
+    draggedTabIndex = index;
+  }
+
+  void removeWindowComponent(DockingWindow window) {
+    window.getTab().setContentComponent(null);
+  }
+
+  void restoreWindowComponent(DockingWindow window) {
+    window.getTab().setContentComponent(window);
+  }
+
+}
\ No newline at end of file
diff --git a/src/net/infonode/docking/DefaultButtonFactories.java b/src/net/infonode/docking/DefaultButtonFactories.java
new file mode 100644
index 0000000..521930a
--- /dev/null
+++ b/src/net/infonode/docking/DefaultButtonFactories.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DefaultButtonFactories.java,v 1.7 2005/04/21 15:38:10 johan Exp $
+package net.infonode.docking;
+
+import net.infonode.gui.button.ButtonFactory;
+import net.infonode.gui.button.FlatButtonFactory;
+
+/**
+ * Contains the default window button factories used in window tabs and {@link TabWindow}'s.
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.7 $
+ * @since IDW 1.1.0
+ */
+public class DefaultButtonFactories {
+  private static final FlatButtonFactory BUTTON_FACTORY = new FlatButtonFactory();
+
+  private DefaultButtonFactories() {
+  }
+
+  /**
+   * Returns the default close button factory.
+   *
+   * @return the default close button factory
+   */
+  public static ButtonFactory getCloseButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+
+  /**
+   * Returns the default minimize button factory.
+   *
+   * @return the default minimize button factory
+   */
+  public static ButtonFactory getMinimizeButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+
+  /**
+   * Returns the default maximize button factory.
+   *
+   * @return the default maximize button factory
+   */
+  public static ButtonFactory getMaximizeButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+
+  /**
+   * Returns the default restore button factory.
+   *
+   * @return the default restore button factory
+   */
+  public static ButtonFactory getRestoreButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+
+  /**
+   * Returns the default undock button factory.
+   *
+   * @return the default undock button factory
+   * @since IDW 1.4.0
+   */
+  public static ButtonFactory getUndockButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+
+  /**
+   * Returns the default dock button factory.
+   *
+   * @return the default dock button factory
+   * @since IDW 1.4.0
+   */
+  public static ButtonFactory getDockButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+}
diff --git a/src/net/infonode/docking/DockingWindow.java b/src/net/infonode/docking/DockingWindow.java
new file mode 100644
index 0000000..68daa79
--- /dev/null
+++ b/src/net/infonode/docking/DockingWindow.java
@@ -0,0 +1,2080 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindow.java,v 1.120 2008/04/04 12:42:15 jesper Exp $
+package net.infonode.docking;
+
+import net.infonode.docking.drag.DockingWindowDragger;
+import net.infonode.docking.drop.ChildDropInfo;
+import net.infonode.docking.drop.DropFilter;
+import net.infonode.docking.drop.SplitDropInfo;
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.WindowAncestors;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.internalutil.DropAction;
+import net.infonode.docking.location.LocationDecoder;
+import net.infonode.docking.model.SplitWindowItem;
+import net.infonode.docking.model.TabWindowItem;
+import net.infonode.docking.model.ViewWriter;
+import net.infonode.docking.model.WindowItem;
+import net.infonode.docking.properties.DockingWindowProperties;
+import net.infonode.docking.title.DockingWindowTitleProvider;
+import net.infonode.docking.title.SimpleDockingWindowTitleProvider;
+import net.infonode.docking.util.DockingUtil;
+import net.infonode.gui.ComponentUtil;
+import net.infonode.gui.EventUtil;
+import net.infonode.gui.mouse.MouseButtonListener;
+import net.infonode.gui.panel.BasePanel;
+import net.infonode.properties.propertymap.*;
+import net.infonode.util.ArrayUtil;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * This is the base class for all types of docking windows. The windows are structured in a tree, typically with a
+ * {@link RootWindow} at the root. Each DockingWindow has a window parent and a number of child windows.
+ * <p>
+ * <b>Warning: </b> the non-public methods in this class can be changed in non-compatible ways in future versions.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.120 $
+ */
+abstract public class DockingWindow extends BasePanel {
+  private static int DROP_FLOATING_YOFFSET = 10;
+
+  /**
+   * Returns the icon for this window.
+   *
+   * @return the icon
+   */
+  abstract public Icon getIcon();
+
+  /**
+   * Returns the child window with index <tt>index</tt>.
+   *
+   * @param index the child window index
+   * @return the child window
+   */
+  abstract public DockingWindow getChildWindow(int index);
+
+  /**
+   * Returns the number of child windows.
+   *
+   * @return the number of child windows
+   */
+  abstract public int getChildWindowCount();
+
+  /**
+ *
+   */
+  abstract protected void doReplace(DockingWindow oldWindow, DockingWindow newWindow);
+
+  /**
+ *
+   */
+  abstract protected void doRemoveWindow(DockingWindow window);
+
+  /**
+ *
+   */
+  abstract protected void update();
+
+  abstract void removeWindowComponent(DockingWindow window);
+
+  abstract void restoreWindowComponent(DockingWindow window);
+
+  private DockingWindow windowParent;
+  private WindowTab tab;
+  private DockingWindow lastFocusedChildWindow;
+  private WindowPopupMenuFactory popupMenuFactory;
+  private ArrayList mouseButtonListeners;
+  private ArrayList listeners;
+
+  private PropertyMapListener propertiesListener = new PropertyMapListener() {
+    public void propertyValuesChanged(PropertyMap propertyMap, Map changes) {
+      doUpdate();
+
+      updateButtonVisibility();
+    }
+  };
+
+  private PropertyMapTreeListener propertyObjectTreeListener = new PropertyMapTreeListener() {
+    public void propertyValuesChanged(Map changes) {
+      doUpdate();
+    }
+  };
+
+  private static HashSet optimizeWindows = new HashSet();
+  private static int optimizeDepth;
+
+  private WindowItem windowItem;
+  private WeakReference lastRootWindow = new WeakReference(null);
+
+  private static int updateModelDepth;
+
+  /**
+ *
+   */
+  protected DockingWindow(WindowItem windowItem) {
+    DockingWindow window = windowItem.getConnectedWindow();
+
+    if (window != null)
+      window.setWindowItem(windowItem.copy());
+
+    this.windowItem = windowItem;
+    this.windowItem.setConnectedWindow(this);
+
+    addMouseListener(new MouseAdapter() {
+      public void mousePressed(MouseEvent e) {
+        if (e.isPopupTrigger()) {
+          showPopupMenu(e);
+        }
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        mousePressed(e);
+      }
+    });
+  }
+
+  /**
+ *
+   */
+  protected void init() {
+    PropertyMapWeakListenerManager.addWeakListener(getWindowProperties().getMap(), propertiesListener);
+    PropertyMapWeakListenerManager.addWeakTreeListener(getPropertyObject(), propertyObjectTreeListener);
+    doUpdate();
+    updateWindowItem(getRootWindow());
+  }
+
+  /**
+ *
+   */
+  private void doUpdate() {
+    update();
+
+    if (tab != null)
+      tab.windowTitleChanged();
+
+    if (windowParent != null && windowParent.getChildWindowCount() == 1)
+      windowParent.doUpdate();
+  }
+
+  protected void addWindowItem(DockingWindow w, int index) {
+    boolean isRestore = w.getWindowItem().isRestoreWindow();
+    windowItem.addWindow(w.getWindowItem(), index);
+
+    if (!isRestore)
+      w.updateWindowItems();
+  }
+  
+  protected void updateWindowItem(DockingWindow w) {
+  	int index = getChildWindowIndex(w);
+  	int modelIndex = index == 0 ? 0 : windowItem.getWindowIndex(getChildWindow(index - 1).getWindowItem()) + 1;
+    windowItem.addWindow(w.getWindowItem(), modelIndex);
+  }
+
+  protected final void updateWindowItems() {
+    windowItem.clearWindows();
+
+    for (int i = 0; i < getChildWindowCount(); i++) {
+      boolean isRestore = getChildWindow(i).windowItem.isRestoreWindow();
+      windowItem.addWindow(getChildWindow(i).windowItem);
+
+      if (!isRestore)
+        getChildWindow(i).updateWindowItems();
+    }
+  }
+
+  /**
+   * <p>
+   * Sets the preferred minimize direction of this window. If the {@link WindowBar} in this direction is enabled this
+   * window will be placed on that bar when {@link #minimize()} is called.
+   * </p>
+   *
+   * <p>
+   * Note that a window will "remember" the last {@link WindowBar} it was added to so the preferred minimize direction
+   * is changed when the window is added to another {@link WindowBar}.
+   * </p>
+   *
+   * @param direction the preferred minimize direction of this window, null (which is default value) means use the
+   *                  closest, enabled {@link WindowBar}
+   * @since IDW 1.3.0
+   */
+  public void setPreferredMinimizeDirection(Direction direction) {
+    windowItem.setLastMinimizedDirection(direction);
+  }
+
+  /**
+   * <p>
+   * Gets the preferred minimize direction of this window. See {@link #setPreferredMinimizeDirection(net.infonode.util.Direction)}
+   * for more information.
+   * </p>
+   *
+   * @return the preferred minimize direction of this window, null if the closest {@link WindowBar} is used
+   * @since IDW 1.3.0
+   */
+  public Direction getPreferredMinimizeDirection() {
+    return windowItem.getLastMinimizedDirection();
+  }
+
+  private ArrayList getMouseButtonListeners() {
+    return mouseButtonListeners;
+  }
+
+  private void setMouseButtonListeners(ArrayList listeners) {
+    mouseButtonListeners = listeners;
+  }
+
+  private ArrayList getListeners() {
+    return listeners;
+  }
+
+  private void setListeners(ArrayList listeners) {
+    this.listeners = listeners;
+  }
+
+  public boolean isUndocked() {
+    return windowParent != null && windowParent.isUndocked();
+  }
+
+  /**
+   * <p>
+   * Adds a listener that receives mouse button events for window tabs. The
+   * listener will be called when a mouse button is pressed, clicked or released
+   * on a window tab of this window or a descendant of this window.
+   * </p>
+   *
+   * <p>
+   * The listeners are called in the reverse order they were added, so the last
+   * added listener will be called first. When all the listeners of this window
+   * has been called, the event is propagated up to the window parent of this
+   * window, if there is one.
+   * </p>
+   *
+   * <p>
+   * The {@link MouseEvent}source is the docking window connected to the tab in
+   * which the mouse event occured. The event point is the mouse coordinate
+   * where the event occured relative to the window.
+   * </p>
+   *
+   * @param listenerDocking the listener
+   * @since IDW 1.3.0
+   */
+  public void addTabMouseButtonListener(MouseButtonListener listenerDocking) {
+    if (getMouseButtonListeners() == null)
+      setMouseButtonListeners(new ArrayList(2));
+
+    getMouseButtonListeners().add(listenerDocking);
+  }
+
+  /**
+   * Removes a mouse button listener that has been previously added using the
+   * {@link #addTabMouseButtonListener(MouseButtonListener)}.
+   *
+   * @param listenerDocking the listener
+   * @since IDW 1.3.0
+   */
+  public void removeTabMouseButtonListener(MouseButtonListener listenerDocking) {
+    if (getMouseButtonListeners() != null) {
+      if (getMouseButtonListeners().remove(listenerDocking) && getMouseButtonListeners().size() == 0)
+        setMouseButtonListeners(null);
+    }
+  }
+
+  void fireTabWindowMouseButtonEvent(MouseEvent event) {
+    fireTabWindowMouseButtonEvent(this, EventUtil.convert(event, this));
+  }
+
+  void fireTabWindowMouseButtonEvent(DockingWindow window, MouseEvent event) {
+    if (getMouseButtonListeners() != null) {
+      MouseButtonListener[] l = (MouseButtonListener[]) getMouseButtonListeners().toArray(
+          new MouseButtonListener[getMouseButtonListeners().size()]);
+
+      for (int i = l.length - 1; i >= 0; i--)
+        l[i].mouseButtonEvent(event);
+    }
+
+    if (windowParent != null)
+      windowParent.fireTabWindowMouseButtonEvent(window, event);
+  }
+
+  /**
+   * Adds a listener which will reveive events for this window and all child windows.
+   *
+   * @param listener the listener
+   * @since IDW 1.1.0
+   */
+  public void addListener(DockingWindowListener listener) {
+    if (getListeners() == null)
+      setListeners(new ArrayList(2));
+
+    getListeners().add(listener);
+  }
+
+  /**
+   * Removes a previously added listener.
+   *
+   * @param listener the listener
+   * @since IDW 1.1.0
+   */
+  public void removeListener(DockingWindowListener listener) {
+    if (getListeners() != null) {
+      getListeners().remove(listener);
+
+      if (getListeners().size() == 0)
+        setListeners(null);
+    }
+  }
+
+  /**
+   * Returns the window parent of this window.
+   *
+   * @return the window parent of this window
+   */
+  public DockingWindow getWindowParent() {
+    return windowParent;
+  }
+
+  /**
+   * Splits this window in the given direction. If this window is a View which is contained in a TabWindow with a single
+   * tab, the TabWindow will splitted instead of this View.
+   *
+   * @param splitWithWindow the splitWithWindow which to split with
+   * @param direction       the split direction
+   * @param dividerLocation the relative split divider location (0 - 1)
+   * @return the resulting split window
+   */
+  public SplitWindow split(final DockingWindow splitWithWindow,
+                           final Direction direction,
+                           final float dividerLocation) {
+    final SplitWindow w = new SplitWindow(direction == Direction.RIGHT || direction == Direction.LEFT);
+
+    optimizeAfter(splitWithWindow.getWindowParent(), new Runnable() {
+      public void run() {
+        getWindowParent().replaceChildWindow(DockingWindow.this, w);
+        w.setWindows(
+            direction == Direction.DOWN || direction == Direction.RIGHT ? DockingWindow.this : splitWithWindow,
+            direction == Direction.UP || direction == Direction.LEFT ? DockingWindow.this : splitWithWindow);
+        w.setDividerLocation(dividerLocation);
+        w.getWindowParent().optimizeWindowLayout();
+      }
+    });
+
+    return w;
+  }
+
+  /**
+   * Starts a drag and drop operation for this window.
+   *
+   * @param dropTarget the {@link RootWindow} in which the window can be dropped
+   * @return an {@link DockingWindowDragger} object which controls the drag and drop operation
+   * @since IDW 1.3.0
+   */
+  public DockingWindowDragger startDrag(RootWindow dropTarget) {
+    return new WindowDragger(this, dropTarget);
+  }
+
+  /**
+   * Returns the properties for this window.
+   *
+   * @return the properties for this window
+   */
+  public DockingWindowProperties getWindowProperties() {
+    return getWindowItem().getDockingWindowProperties();
+  }
+
+  /**
+   * Returns the {@link RootWindow} which contains this window, null if there is none.
+   *
+   * @return the {@link RootWindow}, null if there is none
+   */
+  public RootWindow getRootWindow() {
+    return windowParent == null ? null : windowParent.getRootWindow();
+  }
+
+  /**
+   * Same as {@link #restore()}, but the {@link DockingWindowListener#windowRestoring(DockingWindow)} method of
+   * the window listeners will be called before restoring the window, giving them the possibility to abort the restore
+   * operation.
+   *
+   * @throws OperationAbortedException if the restore operation was aborted by a window listener
+   * @see #restore()
+   * @see DockingWindowListener#windowMinimizing(DockingWindow)
+   * @since IDW 1.4.0
+   */
+  public void restoreWithAbort() throws OperationAbortedException {
+    fireWindowRestoring(this);
+    restore();
+  }
+
+  /**
+   * Restores this window to the location before it was minimized, maximized or closed.
+   * If the window can't be restored to the exact same location, a good approximation is performed. It's not guaranteed
+   * that the window is shown anywhere after this method has returned.
+   */
+  public void restore() {
+    if (isMaximized())
+      doRestoreFromMaximize();
+    else if (isMinimized() || getRootWindow() == null) {
+//    	DockingWindow lastFocused = getLastFocusedChildWindow();
+      ArrayList views = new ArrayList();
+      findViews(views);
+      ArrayList ancestors = new ArrayList();
+
+      for (int i = 0; i < views.size(); i++)
+        ancestors.add(((DockingWindow) views.get(i)).getAncestors());
+
+      restoreViews(views);
+
+      for (int i = 0; i < views.size(); i++) {
+        DockingWindow window = (DockingWindow) views.get(i);
+        window.doFireWindowRestored(window);
+
+        DockingWindow[] a = (DockingWindow[]) ancestors.get(i);
+
+        for (int k = 0; k < a.length; k++)
+          a[k].doFireWindowRestored(window);
+      }
+
+//			if (getRootWindow() != null && lastFocused != null)
+      restoreFocus(); //FocusManager.focusWindow(lastFocused);
+    }
+
+    updateButtonVisibility();
+  }
+
+  private DockingWindow doRestoreFromMaximize() {
+    DockingWindow restoredInWindow = null;
+
+    if (isUndocked()) {
+      FloatingWindow w = DockingUtil.getFloatingWindowFor(this);
+      if (w != null) {
+        w.setMaximizedWindow(null);
+        restoredInWindow = w;
+      }
+    }
+    else {
+      RootWindow w = getRootWindow();
+      if (w != null) {
+        w.setMaximizedWindow(null);
+        restoredInWindow = w;
+      }
+    }
+
+    return restoredInWindow;
+  }
+
+  private ArrayList doRestore() {
+    ArrayList views = new ArrayList();
+    findViews(views);
+    restoreViews(views);
+
+    return views;
+  }
+
+  /**
+   * <p>Removes this window from it's window parent. If the window parent is a split window or a tab window with
+   * one child, it will be removed as well.</p>
+   *
+   * <p>The location of this window is saved and the window can be restored to that location using the
+   * {@link #restore()} method.</p>
+   *
+   * <p>This method will call the {@link DockingWindowListener#windowClosed(DockingWindow)} method of all the listeners
+   * of this window and all window ancestors. The listeners of child windows will not be notified, for example closing
+   * a tab window containing views will not notify the listeners of views in that tab window.</p>
+   */
+  public void close() {
+    if (windowParent != null) {
+      DockingWindow[] ancestors = getAncestors();
+      optimizeAfter(windowParent, new Runnable() {
+        public void run() {
+          windowParent.removeChildWindow(DockingWindow.this);
+        }
+      });
+
+      for (int i = ancestors.length - 1; i >= 0; i--)
+        ancestors[i].fireWindowClosed(this);
+    }
+  }
+
+  /**
+   * Same as {@link #close()}, but the {@link DockingWindowListener#windowClosing(DockingWindow)} method of
+   * the window listeners will be called before closing the window, giving them the possibility to abort the close
+   * operation.
+   *
+   * @throws OperationAbortedException if the close operation was aborted by a window listener
+   * @see #close()
+   * @see DockingWindowListener#windowClosing(DockingWindow)
+   * @since IDW 1.1.0
+   */
+  public void closeWithAbort() throws OperationAbortedException {
+    fireWindowClosing(this);
+    close();
+  }
+
+  /**
+   * <p>Undocks this window from it's window parent i.e. creates a {@link FloatingWindow} containing this window.</p>
+   *
+   * <p>The window can be docked again by calling {@link #dock()}.</p>
+   *
+   * <p>This method will call the {@link DockingWindowListener#windowUndocked(DockingWindow)} method of all the listeners
+   * of this window and all window ancestors. The listeners of child windows will not be notified, for example undocking
+   * a tab window containing views will not notify the listeners of views in that tab window.</p>
+   *
+   * @param location floating window location in screen coordinates
+   * @return the floating window containing the undocked window
+   * @since IDW 1.4.0
+   */
+  public FloatingWindow undock(Point location) {
+    FloatingWindow fw = getRootWindow().createFloatingWindow(this, location);
+    return fw;
+  }
+
+  /**
+   * Same as {@link #undock(Point)}, but the {@link DockingWindowListener#windowUndocking(DockingWindow)} method of
+   * the window listeners will be called before undocking the window, giving them the possibility to abort the undock
+   * operation.
+   *
+   * @param location floating window location in screen coordinates
+   * @return the floating window containing the undocked window
+   * @throws OperationAbortedException if the undock operation was aborted by a window listener
+   * @see #undock(Point)
+   * @see DockingWindowListener#windowClosing(DockingWindow)
+   * @since IDW 1.4.0
+   */
+  public FloatingWindow undockWithAbort(Point location) throws OperationAbortedException {
+    fireWindowUndocking(this);
+    return undock(location);
+  }
+
+
+  /**
+   * <p>Docks the window to the RootWindow to the location it had before it was undocked.</p>
+   *
+   * <p>If the window can't be docked to the exact same location, a good approximation is performed. It's not
+   * guaranteed that the window is shown anywhere after this method has returned.
+   * </p>
+   *
+   * <p>This method will call the {@link DockingWindowListener#windowDocked(DockingWindow)} method of all the listeners
+   * of this window and all window ancestors. The listeners of child windows will not be notified, for example docking
+   * a tab window containing views will not notify the listeners of views in that tab window.</p>
+   *
+   * @since IDW 1.4.0
+   */
+  public void dock() {
+    if (isUndocked()) {
+      ArrayList dockedViews = doRestore();
+
+      updateButtonVisibility();
+
+      fireWindowDocked(dockedViews);
+
+      if (dockedViews.size() > 0 && ((DockingWindow) dockedViews.get(0)).getRootWindow() != null)
+        FocusManager.focusWindow((DockingWindow) dockedViews.get(0));
+    }
+  }
+
+  /**
+   * Same as {@link #dock()}, but the {@link DockingWindowListener#windowDocking(DockingWindow)} method of
+   * the window listeners will be called before docking the window, giving them the possibility to abort the dock
+   * operation.
+   *
+   * @throws OperationAbortedException if the dock operation was aborted by a window listener
+   * @see #dock()
+   * @see DockingWindowListener#windowDocking(DockingWindow)
+   * @since IDW 1.4.0
+   */
+  public void dockWithAbort() throws OperationAbortedException {
+    if (isUndocked()) {
+      fireWindowDocking(this);
+      dock();
+    }
+  }
+
+  /**
+   * Returns the index of a child windows.
+   *
+   * @param window the child window
+   * @return the index of the child window, -1 if the window is not a child of this window
+   */
+  public int getChildWindowIndex(DockingWindow window) {
+    for (int i = 0; i < getChildWindowCount(); i++)
+      if (getChildWindow(i) == window)
+        return i;
+
+    return -1;
+  }
+
+  /**
+   * Returns the popup menu factory for this window. If it's null the window parent popup menu factory will be used
+   * when the mouse popup trigger is activated on this window.
+   *
+   * @return the popup menu factory for this window, null if there is none
+   */
+  public WindowPopupMenuFactory getPopupMenuFactory() {
+    return popupMenuFactory;
+  }
+
+  /**
+   * Sets the popup menu factory for this window. If it's not null a popup menu will be created and shown when the mouse
+   * popup trigger is activated on this window.
+   *
+   * @param popupMenuFactory the popup menu factory, null if no popup menu should be shown
+   */
+  public void setPopupMenuFactory(WindowPopupMenuFactory popupMenuFactory) {
+    this.popupMenuFactory = popupMenuFactory;
+  }
+
+  /**
+   * Returns true if this window is minimized, ie located in a {@link WindowBar}.
+   *
+   * @return true if this window is minimized
+   */
+  public boolean isMinimized() {
+    return windowParent != null && windowParent.isMinimized();
+  }
+
+  /**
+   * Returns the child window that last contained focus.
+   *
+   * @return the child window that last contained focus, null if no child window has contained focus or the child
+   *         has been removed from this window
+   */
+  public DockingWindow getLastFocusedChildWindow() {
+    return lastFocusedChildWindow;
+  }
+
+  /**
+   * Maximizes this window in its root window or in its floating window. If this window has no root window nothing
+   * happens. This method takes the window component and displays it at the top in the root window or in the floating
+   * window. It does NOT modify the window tree structure, ie the window parent remains the unchanged.
+   *
+   * <p>The location of this window is saved and the window can be restored to that location using the
+   * {@link #restore()} method.</p>
+   *
+   * @since IDW 1.1.0
+   */
+  public final void maximize() {
+    if (isUndocked()) {
+      FloatingWindow w = DockingUtil.getFloatingWindowFor(this);
+
+      if (w != null)
+        w.setMaximizedWindow(this);
+    }
+    else {
+      RootWindow rootWindow = getRootWindow();
+
+      if (rootWindow != null)
+        rootWindow.setMaximizedWindow(this);
+    }
+
+    updateButtonVisibility();
+  }
+
+  /**
+   * Same as {@link #maximize()}, but the {@link DockingWindowListener#windowMaximized(DockingWindow)} method of
+   * the window listeners will be called before maximizing the window, giving them the possibility to abort the maximize
+   * operation.
+   *
+   * @throws OperationAbortedException if the maximize operation was aborted by a window listener
+   * @see #maximize()
+   * @see DockingWindowListener#windowMinimizing(DockingWindow)
+   * @since IDW 1.4.0
+   */
+  public void maximizeWithAbort() throws OperationAbortedException {
+    if (!isMaximized()) {
+      fireWindowMaximizing(this);
+      maximize();
+    }
+  }
+
+  /**
+   * Returns true if this window has a root window and is maximized in that root window or in a floating window.
+   *
+   * @return true if this window has a root window and is maximized in that root window or in a floating window
+   * @since IDW 1.1.0
+   */
+  public boolean isMaximized() {
+    DockingWindow w;
+    if (isUndocked()) {
+      FloatingWindow floatingWindow = DockingUtil.getFloatingWindowFor(this);
+      w = floatingWindow != null ? floatingWindow.getMaximizedWindow() : null;
+    }
+    else {
+      RootWindow rootWindow = getRootWindow();
+      w = rootWindow != null ? rootWindow.getMaximizedWindow() : null;
+    }
+    return w == this;
+  }
+
+  /**
+   * Minimizes this window. The window is minimized to the {@link WindowBar} in the preferred minimize direction,
+   * see {@link #setPreferredMinimizeDirection(net.infonode.util.Direction)} and {@link #getPreferredMinimizeDirection()}.
+   * If the {@link WindowBar} in that direction is not enabled, or the direction is null, thiw window is placed on the
+   * closest enabled {@link WindowBar}.
+   * If no suitable {@link WindowBar} was found or this window already is minimized, no action is performed.
+   *
+   * <p>The location of this window is saved and the window can be restored to that location using the
+   * {@link #restore()} method.</p>
+   */
+  public void minimize() {
+    getOptimizedWindow().doMinimize();
+  }
+
+  /**
+   * Minimizes this window to a {@link WindowBar}located in <tt>direction</tt>. If no suitable {@link WindowBar}was
+   * found or this window already is minimized, no action is performed.
+   *
+   * <p>The location of this window is saved and the window can be restored to that location using the
+   * {@link #restore()} method.</p>
+   *
+   * @param direction the direction in which the window bar to be minimized to is located
+   */
+  public void minimize(Direction direction) {
+    doMinimize(direction);
+  }
+
+  /**
+   * Same as {@link #minimize()}, but the {@link DockingWindowListener#windowMinimizing(DockingWindow)} method of
+   * the window listeners will be called before minimizing the window, giving them the possibility to abort the minimize
+   * operation.
+   *
+   * @throws OperationAbortedException if the minimize operation was aborted by a window listener
+   * @see #minimize()
+   * @see DockingWindowListener#windowMinimizing(DockingWindow)
+   * @since IDW 1.4.0
+   */
+  public void minimizeWithAbort() throws OperationAbortedException {
+    if (!isMinimized() && getRootWindow().getClosestWindowBar(this) != null) {
+      fireWindowMinimizing(this);
+      minimize();
+    }
+  }
+
+  /**
+   * Same as {@link #minimize(Direction)}, but the {@link DockingWindowListener#windowMinimizing(DockingWindow)} method of
+   * the window listeners will be called before minimizing the window, giving them the possibility to abort the minimize
+   * operation.
+   *
+   * @throws OperationAbortedException if the minimize operation was aborted by a window listener
+   * @see #minimize(Direction)
+   * @see DockingWindowListener#windowMinimizing(DockingWindow)
+   * @since IDW 1.4.0
+   */
+  public void minimizeWithAbort(Direction direction) throws OperationAbortedException {
+    if (!isMinimized() && getRootWindow().getWindowBar(direction) != null) {
+      fireWindowMinimizing(this);
+      minimize(direction);
+    }
+  }
+
+  private void doMinimize() {
+    doMinimize(windowItem.getLastMinimizedDirection() != null &&
+               getRootWindow().getWindowBar(windowItem.getLastMinimizedDirection()).isEnabled() ?
+                                                                                                windowItem
+                                                                                                    .getLastMinimizedDirection() :
+                                                                                                                                 getRootWindow()
+                                                                                                                                     .getClosestWindowBar(
+                                                                                                                                         this));
+  }
+
+  private void doMinimize(Direction direction) {
+    DockingWindow w = getOptimizedWindow();
+
+    if (direction == null || w.isMinimized())
+      return;
+
+    WindowBar bar = getRootWindow().getWindowBar(direction);
+
+    if (bar != null) {
+      bar.addTab(w);
+      updateButtonVisibility();
+    }
+  }
+
+  /**
+   * Returns true if this window can be minimized by the user.
+   *
+   * @return true if this window can be minimized
+   * @see #minimize()
+   */
+  public boolean isMinimizable() {
+    return getOptimizedWindow().getWindowProperties().getMinimizeEnabled() &&
+           !isUndocked() &&
+           getRootWindow() != null &&
+           getRootWindow().windowBarEnabled();
+  }
+
+  /**
+   * Returns true if this window can be maximized by the user.
+   *
+   * @return true if this window can be maximized
+   * @see #maximize()
+   * @since IDW 1.2.0
+   */
+  public boolean isMaximizable() {
+    return !isMinimized() && /*!isUndocked() &&*/ getOptimizedWindow().getWindowProperties().getMaximizeEnabled();
+  }
+
+  /**
+   * Returns true if this window can be closed by the user.
+   *
+   * @return true if this window can be closed
+   * @see #close()
+   * @see #closeWithAbort()
+   * @since IDW 1.2.0
+   */
+  public boolean isClosable() {
+    return getOptimizedWindow().getWindowProperties().getCloseEnabled();
+  }
+
+  /**
+   * Returns true if this window can be restored by the user.
+   *
+   * @return true if this window can be restored
+   * @see #restore()
+   * @since IDW 1.2.0
+   */
+  public boolean isRestorable() {
+    return getOptimizedWindow().getWindowProperties().getRestoreEnabled();
+  }
+
+  /**
+   * Returns true if this window can be undocked to a floating window.
+   *
+   * @return true if this window can be undocked
+   * @see #undock(Point)
+   * @since IDW 1.4.0
+   */
+  public boolean isUndockable() {
+    return getOptimizedWindow().getWindowProperties().getUndockEnabled();
+  }
+
+  /**
+   * Returns true if this window can be docked to the root window from a floating window.
+   *
+   * @return true if this window can be docked
+   * @see #dock()
+   * @since IDW 1.4.0
+   */
+  public boolean isDockable() {
+    return getOptimizedWindow().getWindowProperties().getDockEnabled();
+  }
+
+  /**
+   * Replaces a child window with another window.
+   *
+   * @param oldWindow the child window to replaceChildWindow
+   * @param newWindow the window to replaceChildWindow it with
+   */
+  public void replaceChildWindow(DockingWindow oldWindow,
+                                 DockingWindow newWindow) {
+    if (oldWindow == newWindow)
+      return;
+
+    DockingWindow nw = internalReplaceChildWindow(oldWindow, newWindow);
+
+    if (getUpdateModel()) {
+      boolean isRestore = nw.getWindowItem().isRestoreWindow();
+      oldWindow.windowItem.replaceWith(nw.getWindowItem());
+
+      if (!isRestore)
+        nw.updateWindowItems();
+
+      cleanUpModel();
+    }
+  }
+
+  protected DockingWindow internalReplaceChildWindow(final DockingWindow oldWindow,
+                                                     final DockingWindow newWindow) {
+    final WindowAncestors oldAncestors = newWindow.storeAncestors();
+    final DockingWindow nw = newWindow.getContentWindow(DockingWindow.this);
+
+    optimizeAfter(newWindow, new Runnable() {
+      public void run() {
+        if (nw == oldWindow)
+          return;
+
+        if (nw.getWindowParent() != null)
+          nw.getWindowParent().removeChildWindow(nw);
+
+        nw.setWindowParent(DockingWindow.this);
+
+        if (oldWindow.isShowingInRootWindow())
+          oldWindow.fireWindowHidden(oldWindow);
+
+        oldWindow.setWindowParent(null);
+
+        if (oldWindow == lastFocusedChildWindow)
+          lastFocusedChildWindow = null;
+
+        doReplace(oldWindow, nw);
+
+        fireTitleChanged();
+
+        oldWindow.fireWindowRemoved(DockingWindow.this, oldWindow);
+        fireWindowRemoved(DockingWindow.this, oldWindow);
+        nw.fireWindowAdded(DockingWindow.this, nw);
+
+        if (nw.isShowingInRootWindow())
+          nw.fireWindowShown(nw);
+
+        newWindow.notifyListeners(oldAncestors);
+      }
+    });
+
+    return nw;
+  }
+
+  /**
+   * Returns the title of this window.
+   *
+   * @return the window title
+   */
+  public String getTitle() {
+    DockingWindowTitleProvider titleProvider = getWindowProperties().getTitleProvider();
+    return (titleProvider == null ? SimpleDockingWindowTitleProvider.INSTANCE : titleProvider).getTitle(this);
+  }
+
+  public String toString() {
+    return getTitle();
+  }
+
+  protected WindowAncestors storeAncestors() {
+    return new WindowAncestors(getAncestors(), isMinimized(), isUndocked());
+  }
+
+  protected void notifyListeners(WindowAncestors ancestors) {
+    if (isMinimized() && !ancestors.isMinimized())
+      fireWindowMinimized(this, ancestors.getAncestors());
+
+    if (isUndocked() && !ancestors.isUndocked())
+      fireWindowUndocked(this, ancestors.getAncestors());
+
+    if (!isUndocked() && ancestors.isUndocked())
+      fireWindowDocked(this, ancestors.getAncestors());
+  }
+
+  protected boolean isShowingInRootWindow() {
+    return windowParent != null && windowParent.isChildShowingInRootWindow(this);
+  }
+
+  protected boolean isChildShowingInRootWindow(DockingWindow child) {
+    return isShowingInRootWindow();
+  }
+
+  /**
+   * Makes this window visible. This causes the tabs of all {@link TabWindow} parents containing this
+   * window to be selected.
+   *
+   * @since IDW 1.1.0
+   */
+  public void makeVisible() {
+    showChildWindow(null);
+  }
+
+  /**
+   * Requests that the last focused child window becomes visible and that focus is restored to the last focused
+   * component in that window. If no child window has had focus or the child window has been removed from this window,
+   * focus is transferred to a child component of this window.
+   *
+   * @since IDW 1.1.0
+   */
+  public void restoreFocus() {
+    if (lastFocusedChildWindow != null)
+      lastFocusedChildWindow.restoreFocus();
+    else {
+      DockingWindow w = getPreferredFocusChild();
+
+      if (w != null)
+        w.restoreFocus();
+      else
+        ComponentUtil.smartRequestFocus(this);
+    }
+  }
+
+  protected DockingWindow getPreferredFocusChild() {
+    return getChildWindowCount() > 0 ? getChildWindow(0) : null;
+  }
+
+  /**
+   * Returns the result after removing unnecessary tab windows which contains only one tab.
+   *
+   * @return the result after removing unnecessary tab windows which contains only one tab
+   */
+  protected DockingWindow getOptimizedWindow() {
+    return this;
+  }
+
+  protected DockingWindow getBestFittedWindow(DockingWindow parentWindow) {
+    return this;
+  }
+
+  protected void internalClose() {
+    optimizeAfter(windowParent, new Runnable() {
+      public void run() {
+        windowParent.removeChildWindow(DockingWindow.this);
+      }
+    });
+  }
+
+  protected void showChildWindow(DockingWindow window) {
+    if (windowParent != null && !isMaximized())
+      windowParent.showChildWindow(this);
+  }
+
+  /**
+   * @return true if this window is inside a tab __exclude__
+   */
+  protected boolean insideTab() {
+    return windowParent == null ? false : windowParent.childInsideTab();
+  }
+
+  /**
+   * @return true if the child windows are inside tabs __exclude__
+   */
+  protected boolean childInsideTab() {
+    return windowParent == null ? false : windowParent.childInsideTab();
+  }
+
+  protected DockingWindow[] getAncestors() {
+    DockingWindow w = this;
+    int count = 0;
+
+    while (w != null) {
+      w = w.getWindowParent();
+      count++;
+    }
+
+    DockingWindow[] windows = new DockingWindow[count];
+    w = this;
+
+    while (w != null) {
+      windows[--count] = w;
+      w = w.getWindowParent();
+    }
+
+    return windows;
+  }
+
+
+  private void fireWindowRemoved(DockingWindow removedFromWindow, DockingWindow removedWindow) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowRemoved(removedFromWindow, removedWindow);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowRemoved(removedFromWindow, removedWindow);
+  }
+
+  protected void fireWindowShown(DockingWindow window) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowShown(window);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowShown(window);
+  }
+
+  protected void fireViewFocusChanged(View previouslyFocusedView, View focusedView) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].viewFocusChanged(previouslyFocusedView, focusedView);
+    }
+  }
+
+  protected void fireWindowHidden(DockingWindow window) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowHidden(window);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowHidden(window);
+  }
+
+  private void fireWindowAdded(DockingWindow addedToWindow, DockingWindow addedWindow) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowAdded(addedToWindow, addedWindow);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowAdded(addedToWindow, addedWindow);
+  }
+
+
+  private void fireWindowClosing(DockingWindow window) throws OperationAbortedException {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowClosing(window);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowClosing(window);
+  }
+
+  private void fireWindowClosed(DockingWindow window) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowClosed(window);
+    }
+  }
+
+  void fireWindowUndocking(DockingWindow window) throws OperationAbortedException {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowUndocking(window);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowUndocking(window);
+  }
+
+  void fireWindowUndocked(DockingWindow window, DockingWindow[] oldAncestors) {
+    doFireWindowUndocked(window);
+
+    for (int i = oldAncestors.length - 1; i >= 0; i--)
+      oldAncestors[i].doFireWindowUndocked(this);
+  }
+
+  private void doFireWindowUndocked(DockingWindow window) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowUndocked(window);
+    }
+  }
+
+  void fireWindowMinimizing(DockingWindow window) throws OperationAbortedException {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowMinimizing(window);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowMinimizing(window);
+  }
+
+  void fireWindowMaximizing(DockingWindow window) throws OperationAbortedException {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowMaximizing(window);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowMaximizing(window);
+  }
+
+  void fireWindowRestoring(DockingWindow window) throws OperationAbortedException {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowRestoring(window);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowRestoring(window);
+  }
+
+  void fireWindowDocking(DockingWindow window) throws OperationAbortedException {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowDocking(window);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowDocking(window);
+  }
+
+  void fireWindowDocked(DockingWindow window, DockingWindow[] oldAncestors) {
+    doFireWindowDocked(window);
+
+    for (int i = oldAncestors.length - 1; i >= 0; i--)
+      oldAncestors[i].doFireWindowDocked(this);
+  }
+
+  void fireWindowDocked(ArrayList dockedViews) {
+    for (int i = 0; i < dockedViews.size(); i++) {
+      DockingWindow window = (DockingWindow) dockedViews.get(i);
+      window.doFireWindowDocked(window);
+
+      DockingWindow[] ancestors = window.getAncestors();
+      for (int k = 0; k < ancestors.length; k++)
+        ancestors[k].doFireWindowDocked(window);
+    }
+  }
+
+  private void doFireWindowDocked(DockingWindow window) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowDocked(window);
+    }
+  }
+
+  private void doFireWindowRestored(DockingWindow window) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowRestored(window);
+    }
+  }
+
+  void fireWindowMaximized(DockingWindow window) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowMaximized(window);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowMaximized(window);
+  }
+
+  void fireWindowMinimized(DockingWindow window, DockingWindow[] oldAncestors) {
+    doFireWindowMinimized(window);
+
+    for (int i = oldAncestors.length - 1; i >= 0; i--)
+      oldAncestors[i].doFireWindowMinimized(window);
+  }
+
+  private void doFireWindowMinimized(DockingWindow window) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowMinimized(window);
+    }
+  }
+
+  void fireWindowRestored(DockingWindow window) {
+    if (getListeners() != null) {
+      DockingWindowListener[] l = (DockingWindowListener[]) getListeners().toArray(
+          new DockingWindowListener[getListeners().size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].windowRestored(window);
+    }
+
+    if (windowParent != null)
+      windowParent.fireWindowRestored(window);
+  }
+
+  protected void setLastMinimizedDirection(Direction direction) {
+    windowItem.setLastMinimizedDirection(direction);
+  }
+
+  /**
+ *
+   */
+  protected void clearChildrenFocus(DockingWindow child, View view) {
+    for (int i = 0; i < getChildWindowCount(); i++)
+      if (child != getChildWindow(i))
+        getChildWindow(i).clearFocus(view);
+  }
+
+  void childGainedFocus(DockingWindow child, View view) {
+    if (child != null)
+      lastFocusedChildWindow = child;
+
+    clearChildrenFocus(child, view);
+
+    if (windowParent != null)
+      windowParent.childGainedFocus(this, view);
+  }
+
+  WindowTab getTab() {
+    if (tab == null) {
+      tab = new WindowTab(this, false);
+    }
+
+    return tab;
+  }
+
+  /**
+ *
+   */
+  protected void childRemoved(DockingWindow child) {
+    if (lastFocusedChildWindow == child)
+      lastFocusedChildWindow = null;
+  }
+
+  /**
+ *
+   */
+  protected void updateButtonVisibility() {
+    if (tab != null)
+      tab.updateTabButtons(null);
+
+    for (int i = 0; i < getChildWindowCount(); i++)
+      getChildWindow(i).updateButtonVisibility();
+  }
+
+  /**
+ *
+   */
+  protected final void readLocations(ObjectInputStream in, RootWindow rootWindow,
+                                     int version) throws IOException {
+    if (version < 3)
+      LocationDecoder.decode(in, rootWindow);  // Just skip location
+
+    if (version > 1) {
+      int index = in.readInt();
+      lastFocusedChildWindow = index == -1 ? null : getChildWindow(index);
+    }
+
+    for (int i = 0; i < getChildWindowCount(); i++)
+      getChildWindow(i).readLocations(in, rootWindow, version);
+  }
+
+  /**
+ *
+   */
+  protected void writeLocations(ObjectOutputStream out) throws IOException {
+    out.writeInt(lastFocusedChildWindow == null ? -1 : getChildWindowIndex(lastFocusedChildWindow));
+
+    for (int i = 0; i < getChildWindowCount(); i++)
+      getChildWindow(i).writeLocations(out);
+  }
+
+  /**
+ *
+   */
+  protected static void beginOptimize(DockingWindow window) {
+    optimizeDepth++;
+
+    if (window != null)
+      optimizeWindows.add(window);
+
+    PropertyMapManager.getInstance().beginBatch();
+  }
+
+  /**
+ *
+   */
+  protected static void endOptimize() {
+    PropertyMapManager.getInstance().endBatch();
+
+    if (--optimizeDepth == 0) {
+      while (optimizeWindows.size() > 0) {
+        HashSet s = optimizeWindows;
+        optimizeWindows = new HashSet();
+
+        for (Iterator it = s.iterator(); it.hasNext();) {
+          DockingWindow window = (DockingWindow) it.next();
+          window.optimizeWindowLayout();
+        }
+      }
+    }
+  }
+
+  /**
+ *
+   */
+  protected static void optimizeAfter(final DockingWindow window, final Runnable runnable) {
+    FocusManager.getInstance().pinFocus(new Runnable() {
+      public void run() {
+        beginOptimize(window);
+
+        try {
+          runnable.run();
+        }
+        finally {
+          endOptimize();
+        }
+      }
+    });
+  }
+
+  /**
+ *
+   */
+  protected boolean needsTitleWindow() {
+    return false;
+  }
+
+  /**
+ *
+   */
+  protected boolean showsWindowTitle() {
+    return false;
+  }
+
+  /**
+ *
+   */
+  protected void optimizeWindowLayout() {
+  }
+
+  /**
+ *
+   */
+  protected DockingWindow getLocationWindow() {
+    return this;
+  }
+
+  /**
+ *
+   */
+  protected void fireTitleChanged() {
+    if (tab != null)
+      tab.windowTitleChanged();
+
+    if (windowParent != null)
+      windowParent.fireTitleChanged();
+  }
+
+  protected DockingWindow getContentWindow(DockingWindow parent) {
+    return needsTitleWindow() && !parent.showsWindowTitle() ? new TabWindow(this) : this;
+  }
+
+  protected final void removeChildWindow(final DockingWindow window) {
+    optimizeAfter(window.getWindowParent(), new Runnable() {
+      public void run() {
+        if (window.isShowingInRootWindow())
+          window.fireWindowHidden(window);
+
+        window.setWindowParent(null);
+
+        if (lastFocusedChildWindow == window)
+          lastFocusedChildWindow = null;
+
+        doRemoveWindow(window);
+        fireTitleChanged();
+        window.fireWindowRemoved(DockingWindow.this, window);
+        fireWindowRemoved(DockingWindow.this, window);
+        afterWindowRemoved(window);
+      }
+    });
+  }
+
+  final protected void removeWindow(DockingWindow window) {
+    window.setWindowParent(null);
+
+    if (getUpdateModel()) {
+      windowItem.removeWindow(windowItem.getChildWindowContaining(window.getWindowItem()));
+      cleanUpModel();
+    }
+  }
+
+  final protected void detach() {
+    DockingWindow oldParent = getWindowParent();
+
+    if (oldParent != null)
+      oldParent.removeChildWindow(this);
+  }
+
+  final protected DockingWindow addWindow(DockingWindow window) {
+    if (window == null)
+      return null;
+
+    DockingWindow w = window.getContentWindow(this);
+    w.detach();
+    w.setWindowParent(this);
+    fireTitleChanged();
+    w.fireWindowAdded(this, w);
+
+    if (w.isShowingInRootWindow())
+      fireWindowShown(w);
+
+    return w;
+  }
+
+  /**
+ *
+   */
+  protected void rootChanged(RootWindow oldRoot, RootWindow newRoot) {
+    if (newRoot != null)
+      lastRootWindow = new WeakReference(newRoot);
+
+    for (int i = 0; i < getChildWindowCount(); i++)
+      if (getChildWindow(i) != null)
+        getChildWindow(i).rootChanged(oldRoot, newRoot);
+
+    updateWindowItem(newRoot);
+  }
+
+  /**
+ *
+   */
+  protected void clearFocus(View view) {
+    for (int i = 0; i < getChildWindowCount(); i++)
+      getChildWindow(i).clearFocus(view);
+  }
+
+  private void setWindowParent(DockingWindow window) {
+    if (window == windowParent)
+      return;
+
+    final RootWindow oldRoot = getRootWindow();
+
+    if (windowParent != null) {
+      if (isMaximized()) {
+        if (isUndocked())
+          DockingUtil.getFloatingWindowFor(this).setMaximizedWindow(null);
+        else
+          getRootWindow().setMaximizedWindow(null);
+      }
+
+      windowParent.childRemoved(this);
+      clearFocus(null);
+
+      if (tab != null)
+        tab.setContentComponent(this);
+    }
+
+    windowParent = window;
+    final RootWindow newRoot = getRootWindow();
+
+    if (oldRoot != newRoot) {
+      rootChanged(oldRoot, newRoot);
+    }
+  }
+
+  private Direction getSplitDirection(Point p) {
+    double[] relativeDist = {p.getX() / getWidth(),
+                             (getWidth() - p.getX()) / getWidth(), p.getY() / getHeight(),
+                             (getHeight() - p.getY()) / getHeight()};
+    int index = ArrayUtil.findSmallest(relativeDist);
+    return index == 0 ? Direction.LEFT : index == 1 ? Direction.RIGHT : index == 2 ? Direction.UP : Direction.DOWN;
+  }
+
+  private int getEdgeDistance(Point p, Direction dir) {
+    return dir == Direction.RIGHT ? getWidth() - p.x :
+           dir == Direction.DOWN ? getHeight() - p.y :
+           dir == Direction.LEFT ? p.x :
+           p.y;
+  }
+
+  DropAction acceptDrop(Point p, DockingWindow window) {
+    DropAction da = null;
+    DockingWindow fw = DockingUtil.getFloatingWindowFor(window);
+    DockingWindow fw2 = DockingUtil.getFloatingWindowFor(this);
+
+    // Check getRootWindow() != window.getRootWindow() so that a view not insidedrop window's root window can be dragged to a floating window
+    if (getRootWindow() != window.getRootWindow() ||
+        ((window.getWindowProperties().getDockEnabled() || fw == null || fw2 != null) &&
+         (window.getWindowProperties().getUndockEnabled() || (fw == fw2)))) {
+      da = !isShowing() || !contains(p) || hasParent(window) || (!getRootWindow().getRootWindowProperties()
+          .getRecursiveTabsEnabled() && insideTab()) ?
+                                                     null : doAcceptDrop(p, window);
+    }
+    //System.out.println(!isShowing() + "  " + !contains(p) + "  " + hasParent(window) + "  " + (!getRootWindow().getRootWindowProperties().getRecursiveTabsEnabled() && insideTab()));
+    //System.out.println(" \n ----- Accept drop: " + (this instanceof
+    // SplitWindow));
+    //System.out.println("isSho " + !isShowing() + " contan " +
+    // !contains(p) + " parent " + hasParent(window) + " bla " +
+    // (!getRootWindow().getRootWindowProperties().getRecursiveTabsEnabled()
+    // && insideTab()));
+    //}
+
+    return da;
+  }
+
+  DropAction getDefaultDropAction() {
+    return new DropAction() {
+      public void execute(DockingWindow window, MouseEvent mouseEvent) {
+        if (getWindowProperties().getUndockEnabled() && getWindowProperties().getUndockOnDropEnabled()) {
+          Point p = mouseEvent.getPoint();
+          Point p2 = SwingUtilities.convertPoint((Component) mouseEvent.getSource(), p, getRootWindow());
+          Point p3 = SwingUtilities.convertPoint((Component) mouseEvent.getSource(), p, getRootWindow().getRootPane());
+          if (!getRootWindow().contains(p2) && !getRootWindow().floatingWindowsContainPoint(p3)) {
+            FloatingWindow fw = DockingUtil.getFloatingWindowFor(window);
+
+            if (fw == null || (fw.getChildWindowCount() > 0 && fw.getChildWindow(0).getChildWindowCount() > 1)) {
+              SwingUtilities.convertPointToScreen(p, (Component) mouseEvent.getSource());
+              p.x = p.x - window.getWidth() / 2;
+              p.y = p.y - Math.min(DROP_FLOATING_YOFFSET, window.getHeight() / 2);
+              try {
+                window.undockWithAbort(p);
+              }
+              catch (OperationAbortedException e) {
+                // Ignore
+              }
+            }
+          }
+        }
+      }
+    };
+  }
+
+  protected boolean acceptsSplitWith(DockingWindow window) {
+    return window != this;
+  }
+
+  protected DropAction doAcceptDrop(Point p, DockingWindow window) {
+    DropAction da = acceptSplitDrop(p, window, getRootWindow().getRootWindowProperties().getEdgeSplitDistance());
+
+    if (da != null)
+      return da;
+
+    da = acceptChildDrop(p, window);
+
+    if (da != null)
+      return da;
+
+    da = acceptInteriorDrop(p, window);
+
+    if (da != null)
+      return da;
+
+    return acceptSplitDrop(p, window, -1);
+  }
+
+  protected DropAction acceptSplitDrop(Point p, DockingWindow window, int splitDistance) {
+    if (!acceptsSplitWith(window))
+      return null;
+
+    Direction splitDir = getSplitDirection(p);
+    int dist = getEdgeDistance(p, splitDir);
+
+    if (splitDistance != -1 && dist > splitDistance * getEdgeDepth(splitDir))
+      return null;
+
+    if (getSplitDropFilter().acceptDrop(new SplitDropInfo(window, this, p, splitDir)))
+      return split(window, splitDir);
+
+    return null;
+  }
+
+  protected DropAction split(DockingWindow window, final Direction splitDir) {
+    int width = splitDir == Direction.LEFT || splitDir == Direction.RIGHT ? getWidth() / 3 : getWidth();
+    int height = splitDir == Direction.DOWN || splitDir == Direction.UP ? getHeight() / 3 : getHeight();
+    int x = splitDir == Direction.RIGHT ? getWidth() - width : 0;
+    int y = splitDir == Direction.DOWN ? getHeight() - height : 0;
+
+    Rectangle rect = new Rectangle(x, y, width, height);
+    getRootWindow().setDragRectangle(SwingUtilities.convertRectangle(this, rect, getRootWindow()));
+
+    return new DropAction() {
+      public void execute(DockingWindow window, MouseEvent mouseEvent) {
+        try {
+          window.beforeDrop(DockingWindow.this);
+          split(window, splitDir, splitDir == Direction.UP || splitDir == Direction.LEFT ? 0.33f : 0.66f);
+          window.restoreFocus();
+        }
+        catch (OperationAbortedException e) {
+          // Ignore
+        }
+      }
+    };
+  }
+
+  protected void beforeDrop(DockingWindow target) throws OperationAbortedException {
+    if (!isMinimized() && target.isMinimized())
+      fireWindowMinimizing(this);
+
+    if (!isUndocked() && target.isUndocked())
+      fireWindowUndocking(this);
+  }
+
+  protected DropAction createTabWindow(DockingWindow window) {
+    getRootWindow().setDragRectangle(SwingUtilities.convertRectangle(getParent(), getBounds(), getRootWindow()));
+
+    return new DropAction() {
+      public void execute(final DockingWindow window, MouseEvent mouseEvent) {
+        optimizeAfter(window.getWindowParent(), new Runnable() {
+          public void run() {
+            try {
+              window.beforeDrop(DockingWindow.this);
+              TabWindow tabWindow = new TabWindow();
+              windowParent.replaceChildWindow(DockingWindow.this, tabWindow);
+              tabWindow.addTab(DockingWindow.this);
+              tabWindow.addTab(window);
+            }
+            catch (OperationAbortedException e) {
+              // Ignore
+            }
+          }
+        });
+      }
+    };
+  }
+
+  protected DropAction acceptInteriorDrop(Point p, DockingWindow window) {
+    return null;
+  }
+
+  /**
+ *
+   */
+  protected boolean hasParent(DockingWindow w) {
+    return w == this || (getWindowParent() != null && getWindowParent().hasParent(w));
+  }
+
+  /**
+ *
+   */
+  protected DockingWindow oldRead(ObjectInputStream in, ReadContext context) throws IOException {
+    windowItem.readSettings(in, context);
+    return this;
+  }
+
+  /**
+ *
+   */
+  abstract protected PropertyMap getPropertyObject();
+
+  /**
+ *
+   */
+  abstract protected PropertyMap createPropertyObject();
+
+  void showPopupMenu(MouseEvent event) {
+    if (event.isConsumed())
+      return;
+
+    DockingWindow w = this;
+
+    while (w.getPopupMenuFactory() == null) {
+      w = w.getWindowParent();
+
+      if (w == null)
+        return;
+    }
+
+    JPopupMenu popupMenu = w.getPopupMenuFactory().createPopupMenu(this);
+
+    if (popupMenu != null && popupMenu.getComponentCount() > 0)
+      popupMenu.show(event.getComponent(), event.getX(), event.getY());
+  }
+
+  protected void setFocused(boolean focused) {
+    if (tab != null)
+      tab.setFocused(focused);
+  }
+
+  protected int getEdgeDepth(Direction dir) {
+    return 1 + (windowParent == null ? 0 : windowParent.getChildEdgeDepth(this, dir));
+  }
+
+  protected int getChildEdgeDepth(DockingWindow window, Direction dir) {
+    return windowParent == null ? 0 : windowParent.getChildEdgeDepth(this, dir);
+  }
+
+  protected DropAction acceptChildDrop(Point p, DockingWindow window) {
+    for (int i = 0; i < getChildWindowCount(); i++) {
+      DockingWindow childWindow = getChildWindow(i);
+      Point p2 = SwingUtilities.convertPoint(this, p, childWindow);
+
+      if (getChildDropFilter().acceptDrop(new ChildDropInfo(window, this, p, childWindow))) {
+        DropAction da = childWindow.acceptDrop(p2, window);
+
+        if (da != null)
+          return da;
+      }
+    }
+
+    return null;
+  }
+
+  protected WindowItem getWindowItem() {
+    return windowItem;
+  }
+
+  protected boolean getUpdateModel() {
+    return updateModelDepth == 0 && windowItem.isRestoreWindow();
+  }
+
+  private void findViews(ArrayList views) {
+    if (this instanceof View)
+      views.add(this);
+
+    for (int i = 0; i < getChildWindowCount(); i++)
+      getChildWindow(i).findViews(views);
+  }
+
+  private void restoreViews(ArrayList views) {
+    for (int i = 0; i < views.size(); i++)
+      ((DockingWindow) views.get(i)).restoreItem();
+  }
+
+  protected static void beginUpdateModel() {
+    updateModelDepth++;
+  }
+
+  protected static void endUpdateModel() {
+    updateModelDepth--;
+  }
+
+  private void restoreItem() {
+    beginUpdateModel();
+
+    try {
+      if (windowItem != null) {
+        WindowItem item = windowItem;
+
+        while (item.getParent() != null) {
+          DockingWindow parentWindow = item.getParent().getConnectedWindow();
+
+          if (parentWindow != null && parentWindow.getRootWindow() != null && !parentWindow.isMinimized() &&
+              !parentWindow.isUndocked()) {
+            if (parentWindow instanceof TabWindow)
+              insertTab((TabWindow) parentWindow, this);
+            else if (parentWindow instanceof RootWindow) {
+              DockingWindow w = getContainer(item.getParent(), windowItem);
+              ((RootWindow) parentWindow).setWindow(w);
+            }
+
+            return;
+          }
+          else {
+            DockingWindow w = null;
+
+            for (int i = 0; i < item.getParent().getWindowCount(); i++) {
+              WindowItem child = item.getParent().getWindow(i);
+
+              if (child != item) {
+                w = child.getVisibleDockingWindow();
+
+                if (w != null)
+                  break;
+              }
+            }
+
+            if (w != null) {
+              final DockingWindow w1 = w;
+              final WindowItem fitem = item;
+
+              optimizeAfter(w.getWindowParent(), new Runnable() {
+                public void run() {
+                  if (fitem.getParent() instanceof SplitWindowItem) {
+                    SplitWindowItem splitWindowItem = (SplitWindowItem) fitem.getParent();
+                    boolean isLeft = splitWindowItem.getWindow(0) == fitem;
+                    SplitWindow newWindow = new SplitWindow(splitWindowItem.isHorizontal(),
+                                                            splitWindowItem.getDividerLocation(),
+                                                            null,
+                                                            null,
+                                                            splitWindowItem);
+                    w1.getWindowParent().internalReplaceChildWindow(w1, newWindow);
+                    DockingWindow w = getContainer(splitWindowItem, windowItem);
+                    DockingWindow w2 = w1.getContainer(splitWindowItem, w1.windowItem);
+                    newWindow.setWindows(isLeft ? w : w2, isLeft ? w2 : w);
+                  }
+                  else if (fitem.getParent() instanceof TabWindowItem) {
+                    TabWindowItem tabWindowItem = (TabWindowItem) fitem.getParent();
+                    TabWindow newWindow = new TabWindow(null, tabWindowItem);
+                    w1.getWindowParent().internalReplaceChildWindow(w1, newWindow);
+                    insertTab(newWindow, DockingWindow.this);
+                    insertTab(newWindow, w1.getOptimizedWindow());
+                  }
+                }
+              });
+
+              return;
+            }
+          }
+
+          item = item.getParent();
+        }
+      }
+
+      final RootWindow rootWindow = (RootWindow) lastRootWindow.get();
+
+      if (rootWindow != null) {
+        final WindowItem topItem = getWindowItem().getTopItem();
+
+        optimizeAfter(null, new Runnable() {
+          public void run() {
+            DockingWindow w = rootWindow.getWindow();
+
+            if (w == null) {
+              WindowItem wi = rootWindow.getWindowItem();
+
+              if (wi.getWindowCount() == 0)
+                wi.addWindow(topItem);
+              else {
+                SplitWindowItem splitWindowItem = new SplitWindowItem();
+                splitWindowItem.addWindow(wi.getWindow(0));
+                splitWindowItem.addWindow(topItem);
+                wi.addWindow(splitWindowItem);
+              }
+
+              rootWindow.setWindow(getContainer(topItem, getWindowItem()));
+            }
+            else {
+              SplitWindow newWindow = new SplitWindow(true);
+              newWindow.getWindowItem().addWindow(rootWindow.getWindowItem().getWindow(0));
+              newWindow.getWindowItem().addWindow(topItem);
+              rootWindow.setWindow(newWindow);
+              newWindow.setWindows(w, getContainer(topItem, getWindowItem()));
+              rootWindow.getWindowItem().addWindow(newWindow.getWindowItem());
+            }
+          }
+        });
+      }
+    }
+    finally {
+      endUpdateModel();
+    }
+  }
+
+  private static void insertTab(TabWindow tabWindow, DockingWindow window) {
+    int index = 0;
+    WindowItem item = tabWindow.getWindowItem();
+    WindowItem childItem = item.getChildWindowContaining(window.getWindowItem());
+
+    for (int i = 0; i < item.getWindowCount(); i++) {
+      WindowItem wi = item.getWindow(i);
+
+      if (wi == childItem)
+        break;
+
+      DockingWindow w = wi.getVisibleDockingWindow();
+
+      if (w != null)
+        index++;
+    }
+
+    tabWindow.addTabNoSelect(window, index);
+    tabWindow.updateSelectedTab();
+  }
+
+  private DockingWindow getContainer(WindowItem topItem, WindowItem item) {
+    if (!needsTitleWindow())
+      return this;
+
+    while (item != topItem) {
+      if (item instanceof TabWindowItem) {
+        TabWindow w = new TabWindow(null, (TabWindowItem) item);
+        w.addTabNoSelect(this, 0);
+        return w;
+      }
+
+      item = item.getParent();
+    }
+
+    TabWindow w = new TabWindow();
+    w.addTabNoSelect(this, 0);
+    item.replaceWith(w.getWindowItem());
+    w.getWindowItem().addWindow(item);
+    return w;
+  }
+
+  private void setWindowItem(WindowItem windowItem) {
+    this.windowItem = windowItem;
+    windowItem.setConnectedWindow(this);
+    updateWindowItem(getRootWindow());
+  }
+
+  protected void updateWindowItem(RootWindow rootWindow) {
+    windowItem.setParentDockingWindowProperties(rootWindow == null ?
+                                                WindowItem.emptyProperties :
+                                                rootWindow.getRootWindowProperties().getDockingWindowProperties());
+  }
+
+  protected void afterWindowRemoved(DockingWindow window) {
+  }
+
+  protected void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+  }
+
+  protected void cleanUpModel() {
+    if (windowParent != null)
+      windowParent.cleanUpModel();
+  }
+/*  static int cc = 0;
+  protected void finalize() {
+		try {
+			System.out.println("\n\ndocking window finalized " + cc++ + "  " + hashCode() + "\n");
+			super.finalize();
+		} catch (Throwable e) {
+			throw new RuntimeException(e);
+		}
+	}*/
+
+  DropFilter getSplitDropFilter() {
+    return getWindowProperties().getDropFilterProperties().getSplitDropFilter();
+  }
+
+  DropFilter getChildDropFilter() {
+    return getWindowProperties().getDropFilterProperties().getChildDropFilter();
+  }
+
+  DropFilter getInteriorDropFilter() {
+    return getWindowProperties().getDropFilterProperties().getInteriorDropFilter();
+  }
+
+  DropFilter getInsertTabDropFilter() {
+    return getWindowProperties().getDropFilterProperties().getInsertTabDropFilter();
+  }
+}
diff --git a/src/net/infonode/docking/DockingWindowAdapter.java b/src/net/infonode/docking/DockingWindowAdapter.java
new file mode 100644
index 0000000..117ab85
--- /dev/null
+++ b/src/net/infonode/docking/DockingWindowAdapter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowAdapter.java,v 1.13 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.docking;
+
+/**
+ * Adapter class which implements the {@link DockingWindowListener} methods with empty bodies.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.13 $
+ * @since IDW 1.1.0
+ */
+public class DockingWindowAdapter implements DockingWindowListener {
+  public void windowShown(DockingWindow window) {
+  }
+
+  public void windowHidden(DockingWindow window) {
+  }
+
+  public void viewFocusChanged(View previouslyFocusedView, View focusedView) {
+  }
+
+  public void windowAdded(DockingWindow addedToWindow, DockingWindow addedWindow) {
+  }
+
+  public void windowRemoved(DockingWindow removedFromWindow, DockingWindow removedWindow) {
+  }
+
+  public void windowClosing(DockingWindow window) throws OperationAbortedException {
+  }
+
+  public void windowClosed(DockingWindow window) {
+  }
+
+  public void windowUndocking(DockingWindow window) throws OperationAbortedException {
+  }
+
+  public void windowUndocked(DockingWindow window) {
+  }
+
+  public void windowDocking(DockingWindow window) throws OperationAbortedException {
+  }
+
+  public void windowDocked(DockingWindow window) {
+  }
+
+  public void windowMinimized(DockingWindow window) {
+  }
+
+  public void windowMaximized(DockingWindow window) {
+  }
+
+  public void windowRestored(DockingWindow window) {
+  }
+
+  public void windowMaximizing(DockingWindow window) throws OperationAbortedException {
+  }
+
+  public void windowMinimizing(DockingWindow window) throws OperationAbortedException {
+  }
+
+  public void windowRestoring(DockingWindow window) throws OperationAbortedException {
+  }
+
+}
diff --git a/src/net/infonode/docking/DockingWindowListener.java b/src/net/infonode/docking/DockingWindowListener.java
new file mode 100644
index 0000000..14bc51c
--- /dev/null
+++ b/src/net/infonode/docking/DockingWindowListener.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowListener.java,v 1.19 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.docking;
+
+/**
+ * <p>
+ * A listener for {@link DockingWindow} events. All events are propagated upwards in the window tree, so
+ * a listener will receive events for the window that it was added to and all descendants of that window.
+ * </p>
+ *
+ * <p>
+ * Note: New methods might be added to this interface in the future. To ensure future compatibility inherit from
+ * {@link DockingWindowAdapter} instead of directly implementing this interface.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.19 $
+ * @since IDW 1.1.0
+ */
+public interface DockingWindowListener {
+  /**
+   * Called when a window has been added.
+   *
+   * @param addedToWindow the parent window that the window was added to
+   * @param addedWindow   the window that was added
+   * @since IDW 1.3.0
+   */
+  void windowAdded(DockingWindow addedToWindow, DockingWindow addedWindow);
+
+  /**
+   * Called when a window has been removed.
+   *
+   * @param removedFromWindow the parent window that the window was removed from
+   * @param removedWindow     the window that was removed
+   * @since IDW 1.3.0
+   */
+  void windowRemoved(DockingWindow removedFromWindow, DockingWindow removedWindow);
+
+  /**
+   * Called when a window is shown, for example when it is selected in a TabWindow.
+   *
+   * @param window the window that was shown
+   * @since IDW 1.3.0
+   */
+  void windowShown(DockingWindow window);
+
+  /**
+   * Called when a window is hidden, for example when it is deselected in a TabWindow.
+   *
+   * @param window the window that was hidden
+   * @since IDW 1.3.0
+   */
+  void windowHidden(DockingWindow window);
+
+  /**
+   * Called when the focus moves from one view to another view.
+   *
+   * @param previouslyFocusedView the view that had focus before the focus moved, null means no view had focus
+   * @param focusedView           the view that got focus, null means no view got focus
+   * @since IDW 1.3.0
+   */
+  void viewFocusChanged(View previouslyFocusedView, View focusedView);
+
+  /**
+   * <p>
+   * Called before the window that this listener is added to, or a child window of that window, is closed.
+   * </p>
+   *
+   * <p>
+   * Note that this method is only called when {@link DockingWindow#closeWithAbort()} is called explicitly, not
+   * when a window is implicitly closed as a result of another method call. Throwing an {@link OperationAbortedException}
+   * will cause the close operation to be aborted.
+   * </p>
+   *
+   * @param window the window that is closing
+   * @throws OperationAbortedException if this exception is thrown the close operation will be aborted
+   */
+  void windowClosing(DockingWindow window) throws OperationAbortedException;
+
+  /**
+   * <p>
+   * Called after the window that this listener is added to, or a child window of that window, has been closed.
+   * </p>
+   *
+   * <p>
+   * Note that this method is only called when {@link DockingWindow#close()} or {@link DockingWindow#closeWithAbort()}
+   * is called explicitly, not when a window is implicitly closed as a result of another method call.
+   * </p>
+   *
+   * @param window the window that has been closed
+   */
+  void windowClosed(DockingWindow window);
+
+  /**
+   * <p>
+   * Called before the window that this listener is added to, or a child window of that window, is undocked.
+   * </p>
+   *
+   * <p>
+   * Note that this method is only called when {@link DockingWindow#undockWithAbort(java.awt.Point)} is called explicitly, not
+   * when a window is implicitly undocked as a result of another method call. Throwing an {@link OperationAbortedException}
+   * will cause the undock operation to be aborted.
+   * </p>
+   *
+   * @param window the window that is undocking
+   * @throws OperationAbortedException if this exception is thrown the undock operation will be aborted
+   * @since IDW 1.4.0
+   */
+  void windowUndocking(DockingWindow window) throws OperationAbortedException;
+
+  /**
+   * <p>
+   * Called after the window that this listener is added to, or a child window of that window, has been undocked.
+   * </p>
+   *
+   * <p>
+   * This method is called when a window is undocked using {@link DockingWindow#undock(java.awt.Point)},
+   * {@link DockingWindow#undockWithAbort(java.awt.Point)} or is added to a window that is undocked.
+   * </p>
+   *
+   * @param window the window that has been undocked
+   * @since IDW 1.4.0
+   */
+  void windowUndocked(DockingWindow window);
+
+  /**
+   * <p>
+   * Called before the window that this listener is added to, or a child window of that window, is docked.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> that this method is only called when {@link DockingWindow#dockWithAbort()} is called explicitly, not
+   * when a window is implicitly docked as a result of another method call. Throwing an {@link OperationAbortedException}
+   * will cause the dock operation to be aborted.
+   * </p>
+   *
+   * @param window the window that is docking
+   * @throws OperationAbortedException if this exception is thrown the dock operation will be aborted i.e. no views in the
+   *                                   window will be docked
+   * @since IDW 1.4.0
+   */
+  void windowDocking(DockingWindow window) throws OperationAbortedException;
+
+  /**
+   * <p>
+   * Called when a view has been docked in the root window.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> If a window containing more than one view was docked then this method will be called for each
+   * view after all views have been docked.
+   * </p>
+   *
+   * @param window the view that has been docked
+   * @since IDW 1.4.0
+   */
+  void windowDocked(DockingWindow window);
+
+  /**
+   * <p>
+   * Called before the window that this listener is added to, or a child window of that window, is minimized.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> that this method is only called when {@link DockingWindow#minimizeWithAbort()} is called
+   * explicitly, not when a window is implicitly docked as a result of another method call. Throwing an
+   * {@link OperationAbortedException} will cause the minimize operation to be aborted.
+   * </p>
+   *
+   * @param window the window that is minimizing
+   * @throws OperationAbortedException if this exception is thrown the minimize operation will be aborted
+   * @since IDW 1.4.0
+   */
+  void windowMinimizing(DockingWindow window) throws OperationAbortedException;
+
+  /**
+   * Called after the window that this listener is added to, or a child window of that window, has been minimized.
+   *
+   * @param window the window that has been minimized
+   * @since IDW 1.4.0
+   */
+  void windowMinimized(DockingWindow window);
+
+  /**
+   * <p>
+   * Called before the window that this listener is added to, or a child window of that window, is maximized.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> that this method is only called when {@link DockingWindow#maximizeWithAbort()} is called
+   * explicitly, not when a window is implicitly docked as a result of another method call. Throwing an
+   * {@link OperationAbortedException} will cause the maximize operation to be aborted.
+   * </p>
+   *
+   * @param window the window that is maximizing
+   * @throws OperationAbortedException if this exception is thrown the maximize operation will be aborted
+   * @since IDW 1.4.0
+   */
+  void windowMaximizing(DockingWindow window) throws OperationAbortedException;
+
+  /**
+   * Called after the window that this listener is added to, or a child window of that window, has been maximized.
+   *
+   * @param window the window that has been maximized
+   * @since IDW 1.4.0
+   */
+  void windowMaximized(DockingWindow window);
+
+  /**
+   * <p>
+   * Called before the window that this listener is added to, or a child window of that window, is restored.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> that this method is only called when {@link DockingWindow#restoreWithAbort()} is called
+   * explicitly, not when a window is implicitly restored as a result of another method call. Throwing an
+   * {@link OperationAbortedException} will cause the restore operation to be aborted.
+   * </p>
+   *
+   * @param window the window that is restoring
+   * @throws OperationAbortedException if this exception is thrown the restore operation will be aborted
+   * @since IDW 1.4.0
+   */
+  void windowRestoring(DockingWindow window) throws OperationAbortedException;
+
+  /**
+   * <p>
+   * Called after the window that this listener is added to, or a child window of that window, has been restored.
+   * </p>
+   *
+   * <p>
+   * Note that this method is only called when {@link DockingWindow#restore()}
+   * is called explicitly, not when a window is implicitly restored as a result of another method call.
+   * </p>
+   *
+   * @param window the window that has been restored
+   * @since IDW 1.4.0
+   */
+  void windowRestored(DockingWindow window);
+}
diff --git a/src/net/infonode/docking/DockingWindowsReleaseInfo.java b/src/net/infonode/docking/DockingWindowsReleaseInfo.java
new file mode 100644
index 0000000..7e64a71
--- /dev/null
+++ b/src/net/infonode/docking/DockingWindowsReleaseInfo.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: DockingWindowsReleaseInfo.java,v 1.4 2004/09/22 14:31:39 jesper Exp $
+package net.infonode.docking;
+
+import net.infonode.util.AntUtils;
+import net.infonode.util.ReleaseInfo;
+
+/**
+ * InfoNode Docking Windows release information. Contains product name, vendor, build time and
+ * version info for the current release.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class DockingWindowsReleaseInfo {
+  private static ReleaseInfo productInfo =
+      new ReleaseInfo("InfoNode Docking Windows GPL",
+                      "NNL Technology AB",
+                      AntUtils.getBuildTime(1235487189453L),
+                      AntUtils.createProductVersion(1, 6, 1),
+                      "GNU General Public License, Version 2",
+                      "http://www.infonode.net");
+
+  private DockingWindowsReleaseInfo() {
+  }
+
+  /**
+   * Gets the release information
+   *
+   * @return Release information
+   */
+  public static ReleaseInfo getReleaseInfo() {
+    return productInfo;
+  }
+}
diff --git a/src/net/infonode/docking/FloatingWindow.java b/src/net/infonode/docking/FloatingWindow.java
new file mode 100644
index 0000000..ca9ae2c
--- /dev/null
+++ b/src/net/infonode/docking/FloatingWindow.java
@@ -0,0 +1,600 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FloatingWindow.java,v 1.51 2007/01/28 21:25:09 jesper Exp $
+package net.infonode.docking;
+
+import net.infonode.docking.drop.ChildDropInfo;
+import net.infonode.docking.drop.InteriorDropInfo;
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.WindowAncestors;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.internalutil.DropAction;
+import net.infonode.docking.model.FloatingWindowItem;
+import net.infonode.docking.model.ViewReader;
+import net.infonode.docking.model.ViewWriter;
+import net.infonode.docking.properties.DockingWindowProperties;
+import net.infonode.docking.properties.FloatingWindowProperties;
+import net.infonode.docking.properties.SplitWindowProperties;
+import net.infonode.docking.util.DockingUtil;
+import net.infonode.gui.ComponentUtil;
+import net.infonode.gui.layout.StretchLayout;
+import net.infonode.gui.panel.SimplePanel;
+import net.infonode.gui.shaped.panel.ShapedPanel;
+import net.infonode.properties.gui.InternalPropertiesUtil;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.properties.propertymap.PropertyMapTreeListener;
+import net.infonode.properties.propertymap.PropertyMapWeakListenerManager;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Map;
+
+/**
+ * <p>
+ * A window that is floating on-top of the root window and containing another docking window.
+ * </p>
+ *
+ * <p>
+ * A window can be maximized inside the floating window just as in a root window.
+ * </p>
+ *
+ * <p>
+ * After a floating window has been closed it shouldn't be reused again.
+ * </p>
+ *
+ * <p>
+ * Floating window inherits its component properties and shaped panel properties from the root
+ * window's window area. It is possible to set specific component and shaped panel properties for
+ * a floating window in the {@link net.infonode.docking.properties.FloatingWindowProperties}, see
+ * {@link FloatingWindow#getFloatingWindowProperties()}.
+ * </p>
+ *
+ * <p>
+ * A floating window is created by calling the
+ * {@link net.infonode.docking.RootWindow#createFloatingWindow(Point, Dimension, DockingWindow)}
+ * method or indirectly created by calling the
+ * {@link net.infonode.docking.DockingWindow#undock(Point)} method.
+ * </p>
+ *
+ * <p>
+ * It's possible to add a menu bar to the floating window. Just call:
+ * </p>
+ * <pre>
+ *   myFloatingWindow.getRootPane().setJMenuBar(myMenuBar);
+ * </pre>
+ *
+ * <p>
+ * The floating window is placed as the BorderLayout.CENTER component of the content pane of the
+ * root pane. You can add additional components in the other BorderLayout positions. Example, add a
+ * status label at the bottom:
+ * </p>
+ * <pre>
+ *   myFloatingWindow.getRootPane().getContentPane().add(myStstusLabel, BroderLayout.SOUTH);
+ * </pre>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.51 $
+ * @since IDW 1.4.0
+ */
+public class FloatingWindow extends DockingWindow {
+  private DockingWindow window;
+  private Window dialog;
+  private JPanel dragPanel = new SimplePanel();
+  private ShapedPanel shapedPanel;
+  private DockingWindow maximizedWindow;
+  private Runnable titleUpdater;
+
+  private AWTEventListener awtMouseEventListener;
+
+  private PropertyMapTreeListener propertiesListener = new PropertyMapTreeListener() {
+    public void propertyValuesChanged(Map changes) {
+      updateFloatingWindow(changes);
+    }
+  };
+
+  FloatingWindow(RootWindow rootWindow) {
+    super(new FloatingWindowItem());
+
+    getFloatingWindowProperties().addSuperObject(rootWindow.getRootWindowProperties().getFloatingWindowProperties());
+
+    setLayout(new StretchLayout(true, true));
+    shapedPanel = new ShapedPanel();
+    setComponent(shapedPanel);
+
+    Component c = rootWindow.getTopLevelComponent();
+    dialog = getFloatingWindowProperties().getUseFrame() ? (Window) new JFrame() :
+             (Window) (c instanceof Frame ? new JDialog((Frame) c) : new JDialog((Dialog) c));
+    ((RootPaneContainer) dialog).getContentPane().add(this, BorderLayout.CENTER);
+
+    if (dialog instanceof JDialog)
+      ((JDialog) dialog).setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+    else
+      ((JFrame) dialog).setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+
+    dialog.addWindowListener(new WindowAdapter() {
+      public void windowClosing(WindowEvent e) {
+        try {
+          if (getWindowProperties().getCloseEnabled())
+            closeWithAbort();
+        }
+        catch (OperationAbortedException e1) {
+          // Ignore
+        }
+      }
+    });
+
+    JRootPane rp = ((RootPaneContainer) dialog).getRootPane();
+    rp.getLayeredPane().add(dragPanel);
+    rp.getLayeredPane().setLayer(dragPanel, JLayeredPane.DRAG_LAYER.intValue());
+    dragPanel.setVisible(false);
+
+    dragPanel.addMouseListener(new MouseAdapter() {
+      public void mouseEntered(MouseEvent e) {
+        getRootWindow().setCurrentDragRootPane(getRootPane());
+      }
+
+      public void mouseExited(MouseEvent e) {
+        if (!dragPanel.contains(e.getPoint()) && getRootWindow().getCurrentDragRootPane() == getRootPane())
+          getRootWindow().setCurrentDragRootPane(null);
+      }
+    });
+
+    if (rootWindow.isHeavyweightSupported()) {
+      try {
+        awtMouseEventListener = new AWTEventListener() {
+          public void eventDispatched(AWTEvent event) {
+            if (event.getID() == MouseEvent.MOUSE_ENTERED) {
+              Component c = (Component) event.getSource();
+              if (ComponentUtil.getTopLevelAncestor(c) == dialog) {
+                getRootWindow().setCurrentDragRootPane(getRootPane());
+              }
+            }
+          }
+        };
+        Toolkit.getDefaultToolkit().addAWTEventListener(awtMouseEventListener, AWTEvent.MOUSE_EVENT_MASK);
+      }
+      catch (SecurityException e) {
+        awtMouseEventListener = null;
+        // Ignore
+      }
+    }
+
+    PropertyMapWeakListenerManager.addWeakTreeListener(getFloatingWindowProperties().getMap(), propertiesListener);
+    updateFloatingWindow(null);
+  }
+
+  FloatingWindow(RootWindow rootWindow, DockingWindow window, Point p, Dimension internalSize) {
+    this(rootWindow);
+    setWindow(window);
+    setInternalSize(internalSize);
+    dialog.setLocation(p.x, p.y);
+  }
+
+  /**
+   * Sets the top level docking window inside this floating window.
+   *
+   * @param newWindow the top level docking window, null for no window i.e. empty floating window
+   */
+  public void setWindow(DockingWindow newWindow) {
+    if (window == newWindow)
+      return;
+
+    if (window == null) {
+      WindowAncestors ancestors = newWindow.storeAncestors();
+      DockingWindow actualWindow = addWindow(newWindow);
+      doReplace(null, actualWindow);
+      newWindow.notifyListeners(ancestors);
+    }
+    else if (newWindow == null) {
+      removeChildWindow(window);
+      window = null;
+    }
+    else
+      replaceChildWindow(window, newWindow);
+  }
+
+  /**
+   * Returns the top level docking window inside this floating window.
+   *
+   * @return the top level docking window inside this floating window
+   */
+  public DockingWindow getWindow() {
+    return window;
+  }
+
+  /**
+   * Sets the maximized window in this floating window.
+   *
+   * @param window the window to maximize, must be a member of the window tree inside this floating window
+   */
+  public void setMaximizedWindow(DockingWindow window) {
+    if (window == maximizedWindow)
+      return;
+
+    if (window instanceof FloatingWindow || window != null && !(DockingUtil.getFloatingWindowFor(window) == this))
+      return;
+
+    internalSetMaximizedWindow(window);
+  }
+
+  /**
+   * Returns the maximized window in this floating window.
+   *
+   * @return maximized window or null if no window is maximized
+   */
+  public DockingWindow getMaximizedWindow() {
+    return maximizedWindow;
+  }
+
+  /**
+   * <p>
+   * Returns the property values for this floating window.
+   * </p>
+   *
+   * <p>
+   * Floating window inherits its component properties and shaped panel properties from the root
+   * window's window area. It is possible to set specific component and shaped panel properties for
+   * a floating window in the {@link net.infonode.docking.properties.FloatingWindowProperties}.
+   * </p>
+   *
+   * @return the property values for this floating window
+   */
+  public FloatingWindowProperties getFloatingWindowProperties() {
+    return ((FloatingWindowItem) getWindowItem()).getFloatingWindowProperties();
+  }
+
+  /**
+   * <p>
+   * Returns the properties for this window.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> A floating window only uses the close enabled and title provider
+   * properties of the docking window properties.
+   * </p>
+   *
+   * @return the properties for this window
+   */
+  public DockingWindowProperties getWindowProperties() {
+    return super.getWindowProperties();
+  }
+
+  /**
+   * Floating window cannot be minimized
+   */
+  public void minimize() {
+  }
+
+  /**
+   * Floating window cannot be minimized
+   *
+   * @param direction
+   */
+  public void minimize(Direction direction) {
+  }
+
+  public boolean isDockable() {
+    return false;
+  }
+
+  public boolean isMaximizable() {
+    return false;
+  }
+
+  public boolean isMinimizable() {
+    return false;
+  }
+
+  public boolean isRestorable() {
+    return false;
+  }
+
+  public boolean isUndockable() {
+    return false;
+  }
+
+  public void close() {
+    PropertyMapWeakListenerManager.removeWeakTreeListener(getFloatingWindowProperties().getMap(), propertiesListener);
+    RootWindow rw = getRootWindow();
+
+    super.close();
+
+    dialog.dispose();
+    if (rw != null)
+      rw.removeFloatingWindow(this);
+
+    try {
+      if (awtMouseEventListener != null)
+        Toolkit.getDefaultToolkit().removeAWTEventListener(awtMouseEventListener);
+    }
+    catch (SecurityException e) {
+      // Ignore
+    }
+  }
+
+  public Icon getIcon() {
+    return window == null ? null : window.getIcon();
+  }
+
+  public DockingWindow getChildWindow(int index) {
+    return window;
+  }
+
+  public int getChildWindowCount() {
+    return window == null ? 0 : 1;
+  }
+
+  public boolean isUndocked() {
+    return true;
+  }
+
+  void startDrag() {
+    JRootPane rp = ((RootPaneContainer) dialog).getRootPane();
+    dragPanel.setBounds(0, 0, rp.getWidth(), rp.getHeight());
+    dragPanel.setVisible(true);
+    //ComponentUtil.validate(rp.getLayeredPane());
+    //rp.validate();
+  }
+
+  void stopDrag() {
+//    JRootPane rp = dialog.getRootPane();
+    dragPanel.setVisible(false);
+    //rp.getLayeredPane().remove(dragPanel);
+  }
+
+  JPanel getDragPanel() {
+    return dragPanel;
+  }
+
+  boolean windowContainsPoint(Point p) {
+    return getTopLevelAncestor().contains(SwingUtilities.convertPoint(this, p, getTopLevelAncestor()));
+  }
+
+  private void internalSetMaximizedWindow(DockingWindow window) {
+    if (window == maximizedWindow)
+      return;
+
+    DockingWindow focusWindow = null;
+
+    if (maximizedWindow != null) {
+      DockingWindow oldMaximized = maximizedWindow;
+      maximizedWindow = null;
+
+      if (oldMaximized.getWindowParent() != null)
+        oldMaximized.getWindowParent().restoreWindowComponent(oldMaximized);
+
+      if (oldMaximized != this.window)
+        shapedPanel.remove(oldMaximized);
+
+      focusWindow = oldMaximized;
+      fireWindowRestored(oldMaximized);
+    }
+
+    maximizedWindow = window;
+
+    if (maximizedWindow != null) {
+      if (maximizedWindow.getWindowParent() != null)
+        maximizedWindow.getWindowParent().removeWindowComponent(maximizedWindow);
+
+      if (maximizedWindow != this.window) {
+        shapedPanel.add(maximizedWindow);
+
+        if (this.window != null)
+          this.window.setVisible(false);
+      }
+
+      maximizedWindow.setVisible(true);
+
+      focusWindow = maximizedWindow;
+      fireWindowMaximized(maximizedWindow);
+    }
+    else if (this.window != null) {
+      this.window.setVisible(true);
+    }
+
+    if (focusWindow != null)
+      FocusManager.focusWindow(focusWindow);
+  }
+
+  protected void doReplace(DockingWindow oldWindow, DockingWindow newWindow) {
+    if (oldWindow == window) {
+      if (window != null) {
+        shapedPanel.remove(window);
+        window.setVisible(true);
+      }
+
+      window = newWindow;
+
+      if (window != null) {
+        if (maximizedWindow != null)
+          window.setVisible(false);
+
+        shapedPanel.add(window);
+        doUpdateTitle();
+        shapedPanel.revalidate();
+      }
+    }
+
+    updateButtonVisibility();
+  }
+
+  protected void doRemoveWindow(DockingWindow window) {
+    if (window != null) {
+      shapedPanel.remove(window);
+      this.window.setVisible(true);
+      this.window = null;
+      shapedPanel.repaint();
+    }
+  }
+
+  protected void afterWindowRemoved(DockingWindow window) {
+    if (getFloatingWindowProperties().getAutoCloseEnabled())
+      close();
+  }
+
+  private void doUpdateTitle() {
+    if (titleUpdater == null) {
+      titleUpdater = new Runnable() {
+        public void run() {
+          if (dialog != null) {
+            if (dialog instanceof Dialog)
+              ((Dialog) dialog).setTitle(window == null ? "" : window.getTitle());
+            else
+              ((Frame) dialog).setTitle(window == null ? "" : window.getTitle());
+          }
+
+          titleUpdater = null;
+        }
+      };
+
+      SwingUtilities.invokeLater(titleUpdater);
+    }
+  }
+
+  protected boolean acceptsSplitWith(DockingWindow window) {
+    return false;
+  }
+
+  protected DropAction doAcceptDrop(Point p, DockingWindow window) {
+    DockingWindow dropWindow = maximizedWindow != null ? maximizedWindow : this.window;
+
+    if (dropWindow != null) {
+      Point p2 = SwingUtilities.convertPoint(this, p, dropWindow);
+
+      if (dropWindow.contains(p2)) {
+        return getChildDropFilter().acceptDrop(new ChildDropInfo(window, this, p, dropWindow)) ?
+               dropWindow.acceptDrop(p2, window) :
+               null;
+      }
+    }
+
+    return super.doAcceptDrop(p, window);
+  }
+
+  protected DropAction acceptInteriorDrop(Point p, DockingWindow window) {
+    if (this.window != null)
+      return null;
+
+    getRootWindow().setDragRectangle(null);
+
+    if (getInteriorDropFilter().acceptDrop(new InteriorDropInfo(window, this, p)))
+      return new DropAction() {
+        public void execute(DockingWindow window, MouseEvent mouseEvent) {
+          setWindow(window);
+        }
+      };
+
+    return null;
+  }
+
+  protected void update() {
+  }
+
+  void removeWindowComponent(DockingWindow window) {
+    // Do nothing
+  }
+
+  void restoreWindowComponent(DockingWindow window) {
+    // Do nothing
+  }
+
+  protected void showChildWindow(DockingWindow window) {
+    if (maximizedWindow != null && window == this.window)
+      setMaximizedWindow(null);
+
+    super.showChildWindow(window);
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return new SplitWindowProperties().getMap();
+  }
+
+  protected PropertyMap createPropertyObject() {
+    return new SplitWindowProperties().getMap();
+  }
+
+  private void updateFloatingWindow(Map map) {
+    FloatingWindowProperties properties = getFloatingWindowProperties();
+    ComponentProperties componentProperties = map == null ||
+                                              map.get(properties.getComponentProperties().getMap()) != null ?
+                                                                                                            properties
+                                                                                                                .getComponentProperties() :
+                                                                                                                                          null;
+    ShapedPanelProperties shapedProperties = map == null ||
+                                             map.get(properties.getShapedPanelProperties().getMap()) != null ?
+                                                                                                             properties
+                                                                                                                 .getShapedPanelProperties() :
+                                                                                                                                             null;
+
+    if (componentProperties != null)
+      componentProperties.applyTo(shapedPanel);
+
+    if (shapedProperties != null)
+      InternalPropertiesUtil.applyTo(shapedProperties, shapedPanel);
+  }
+
+  protected void fireTitleChanged() {
+    super.fireTitleChanged();
+
+    doUpdateTitle();
+  }
+
+  private void setInternalSize(Dimension size) {
+    ((RootPaneContainer) dialog).getRootPane().setPreferredSize(size);
+    dialog.pack();
+    ((RootPaneContainer) dialog).getRootPane().setPreferredSize(null);
+  }
+
+  protected DockingWindow read(ObjectInputStream in, ReadContext context, ViewReader viewReader) throws IOException {
+    dialog.setSize(new Dimension(in.readInt(), in.readInt()));
+    dialog.setLocation(in.readInt(), in.readInt());
+
+    dialog.setVisible(in.readBoolean());
+    getWindowItem().readSettings(in, context);
+
+    if (in.readBoolean())
+      setWindow(WindowDecoder.decodeWindow(in, context, viewReader));
+
+    return this;
+  }
+
+  protected void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+    out.writeInt(dialog.getWidth());
+    out.writeInt(dialog.getHeight());
+    out.writeInt(dialog.getX());
+    out.writeInt(dialog.getY());
+    out.writeBoolean(dialog.isVisible());
+    getWindowItem().writeSettings(out, context);
+    out.writeBoolean(window != null);
+
+    if (window != null)
+      window.write(out, context, viewWriter);
+  }
+}
diff --git a/src/net/infonode/docking/FocusManager.java b/src/net/infonode/docking/FocusManager.java
new file mode 100644
index 0000000..afb65a7
--- /dev/null
+++ b/src/net/infonode/docking/FocusManager.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FocusManager.java,v 1.13 2007/01/28 19:00:30 jesper Exp $
+package net.infonode.docking;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.13 $
+ */
+class FocusManager {
+  private static final FocusManager INSTANCE = new FocusManager();
+
+  private int ignoreFocusChanges;
+  private Timer focusTimer = new Timer(20, new ActionListener() {
+    public void actionPerformed(ActionEvent e) {
+      updateFocus();
+      focusUpdateTriggered = false;
+    }
+  });
+  private boolean focusUpdateTriggered;
+  private ArrayList lastFocusedWindows = new ArrayList();
+  private Component focusedComponent;
+  private PropertyChangeListener focusListener = new PropertyChangeListener() {
+    public void propertyChange(PropertyChangeEvent evt) {
+      if (ignoreFocusChanges > 0)
+        return;
+
+      ignoreFocusChanges++;
+
+      try {
+        triggerFocusUpdate();
+      }
+      finally {
+        ignoreFocusChanges--;
+      }
+    }
+
+    private void triggerFocusUpdate() {
+      if (focusUpdateTriggered)
+        return;
+
+      focusUpdateTriggered = true;
+      focusTimer.setRepeats(false);
+      focusTimer.start();
+    }
+
+  };
+
+  private FocusManager() {
+    KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", focusListener);
+    updateFocus();
+  }
+
+  static FocusManager getInstance() {
+    return INSTANCE;
+  }
+
+  private void updateFocus() {
+    focusedComponent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
+//    System.out.println("Focus: " + System.identityHashCode(focusedComponent) + ", " + focusedComponent);
+    ArrayList oldFocusedWindows = lastFocusedWindows;
+    lastFocusedWindows = new ArrayList();
+    updateWindows(focusedComponent, focusedComponent, oldFocusedWindows);
+
+    for (int i = 0; i < oldFocusedWindows.size(); i++) {
+      RootWindow w = (RootWindow) ((Reference) oldFocusedWindows.get(i)).get();
+
+      if (w != null)
+        w.setFocusedView(null);
+    }
+
+  }
+
+  void pinFocus(Runnable runnable) {
+    ignoreFocusChanges++;
+    final Component c = focusedComponent;
+
+    try {
+      runnable.run();
+    }
+    finally {
+      if (--ignoreFocusChanges == 0 && c != null) {
+        c.requestFocusInWindow();
+
+        SwingUtilities.invokeLater(new Runnable() {
+          public void run() {
+            SwingUtilities.invokeLater(new Runnable() {
+              public void run() {
+                c.requestFocusInWindow();
+              }
+            });
+          }
+        });
+      }
+    }
+  }
+
+  void startIgnoreFocusChanges() {
+    ignoreFocusChanges++;
+  }
+
+  void stopIgnoreFocusChanges() {
+    if (--ignoreFocusChanges == 0) {
+      updateFocus();
+    }
+  }
+
+  static void focusWindow(final DockingWindow window) {
+    if (window == null)
+      return;
+
+    window.restoreFocus();
+
+    SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        SwingUtilities.invokeLater(new Runnable() {
+          public void run() {
+            window.restoreFocus();
+          }
+        });
+      }
+    });
+  }
+
+  private static View getViewContaining(Component component) {
+    return component == null ?
+           null : component instanceof View ? (View) component : getViewContaining(component.getParent());
+  }
+
+  private void updateWindows(Component focusedComponent, Component component, ArrayList oldFocusedWindows) {
+    while (true) {
+      View view = getViewContaining(component);
+
+      if (view == null)
+        break;
+
+      view.setLastFocusedComponent(focusedComponent);
+      RootWindow rw = view.getRootWindow();
+
+      if (rw == null)
+        break;
+
+      rw.setFocusedView(view);
+      lastFocusedWindows.add(new WeakReference(rw));
+      component = rw;
+
+      for (int i = 0; i < oldFocusedWindows.size(); i++) {
+        if (((Reference) oldFocusedWindows.get(i)).get() == component)
+          oldFocusedWindows.remove(i);
+      }
+    }
+  }
+
+}
diff --git a/src/net/infonode/docking/OperationAbortedException.java b/src/net/infonode/docking/OperationAbortedException.java
new file mode 100644
index 0000000..cf2241f
--- /dev/null
+++ b/src/net/infonode/docking/OperationAbortedException.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: OperationAbortedException.java,v 1.3 2004/09/28 15:07:29 jesper Exp $
+package net.infonode.docking;
+
+/**
+ * Exception thrown when an operation is aborted.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class OperationAbortedException extends Exception {
+  /**
+   * Constructor.
+   */
+  public OperationAbortedException() {
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param message the exception message
+   */
+  public OperationAbortedException(String message) {
+    super(message);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param cause the exception cause
+   */
+  public OperationAbortedException(Throwable cause) {
+    super(cause);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param message the exception message
+   * @param cause   the exception cause
+   */
+  public OperationAbortedException(String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/src/net/infonode/docking/RectangleBorderComponent.java b/src/net/infonode/docking/RectangleBorderComponent.java
new file mode 100644
index 0000000..2aea2f4
--- /dev/null
+++ b/src/net/infonode/docking/RectangleBorderComponent.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RectangleBorderComponent.java,v 1.9 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ */
+class RectangleBorderComponent extends JComponent {
+  private int lineWidth;
+
+  RectangleBorderComponent(int lineWidth) {
+    this.lineWidth = lineWidth;
+    setOpaque(false);
+  }
+
+  void setLineWidth(int lineWidth) {
+    this.lineWidth = lineWidth;
+  }
+
+  public void paint(Graphics g) {
+    g.setColor(Color.BLACK);
+    g.setXORMode(Color.WHITE);
+    g.fillRect(lineWidth, 0, getWidth() - 2 * lineWidth, lineWidth);
+    g.fillRect(lineWidth, getHeight() - 1 - lineWidth, getWidth() - 2 * lineWidth, lineWidth);
+    g.fillRect(0, 0, lineWidth, getHeight() - 1);
+    g.fillRect(getWidth() - lineWidth, 0, lineWidth, getHeight() - 1);
+    g.setPaintMode();
+  }
+}
diff --git a/src/net/infonode/docking/RootWindow.java b/src/net/infonode/docking/RootWindow.java
new file mode 100644
index 0000000..93955a5
--- /dev/null
+++ b/src/net/infonode/docking/RootWindow.java
@@ -0,0 +1,1305 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RootWindow.java,v 1.129 2009/02/05 15:57:55 jesper Exp $
+package net.infonode.docking;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import javax.swing.*;
+
+import net.infonode.docking.action.*;
+import net.infonode.docking.drop.ChildDropInfo;
+import net.infonode.docking.drop.InteriorDropInfo;
+import net.infonode.docking.internal.HeavyWeightContainer;
+import net.infonode.docking.internal.HeavyWeightDragRectangle;
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.internalutil.DropAction;
+import net.infonode.docking.model.*;
+import net.infonode.docking.properties.RootWindowProperties;
+import net.infonode.docking.util.DockingUtil;
+import net.infonode.gui.CursorManager;
+import net.infonode.gui.DragLabelWindow;
+import net.infonode.gui.componentpainter.ComponentPainter;
+import net.infonode.gui.componentpainter.RectangleComponentPainter;
+import net.infonode.gui.layout.BorderLayout2;
+import net.infonode.gui.layout.LayoutUtil;
+import net.infonode.gui.layout.StretchLayout;
+import net.infonode.gui.mouse.MouseButtonListener;
+import net.infonode.gui.panel.SimplePanel;
+import net.infonode.gui.shaped.panel.ShapedPanel;
+import net.infonode.properties.gui.InternalPropertiesUtil;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.properties.propertymap.PropertyMapManager;
+import net.infonode.util.ArrayUtil;
+import net.infonode.util.Direction;
+import net.infonode.util.ReadWritable;
+
+/**
+ * The root window is a top level container for docking windows. Docking windows can't be dragged outside of their root
+ * window. The property values of a root window is inherited to the docking windows inside it.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.129 $
+ */
+public class RootWindow extends DockingWindow implements ReadWritable {
+  private static final int SERIALIZE_VERSION = 4;
+
+  private static final int FLOATING_WINDOW_MIN_WIDTH = 400;
+  private static final int FLOATING_WINDOW_MIN_HEIGHT = 300;
+
+  private boolean heavyweightSupport = false;
+
+  private class SingleComponentLayout implements LayoutManager {
+    public void addLayoutComponent(String name, Component comp) {
+    }
+
+    public void layoutContainer(Container parent) {
+      Dimension size = LayoutUtil.getInteriorSize(parent);
+      Insets insets = parent.getInsets();
+      mainPanel.setBounds(insets.left, insets.top, size.width, size.height);
+
+      int w1 = windowBars[Direction.LEFT.getValue()].getPreferredSize().width;
+      int w2 = windowBars[Direction.RIGHT.getValue()].getPreferredSize().width;
+      int h1 = windowBars[Direction.UP.getValue()].getPreferredSize().height;
+      int h2 = windowBars[Direction.DOWN.getValue()].getPreferredSize().height;
+
+      final Direction[] directions = Direction.getDirections();
+
+      for (int i = 0; i < windowBars.length; i++) {
+        Component panel = windowBars[i].getEdgePanel();
+
+        if (panel.isVisible()) {
+          Direction dir = directions[i];
+          int maxWidth = size.width - w1 - w2;
+          int maxHeight = size.height - h1 - h2;
+
+          if (dir == Direction.RIGHT) {
+            int rightX = parent.getWidth() - insets.right - w2 + windowBars[dir.getValue()].getInsets().left;
+            int width = Math.min(panel.getPreferredSize().width,
+                maxWidth + windowBars[dir.getValue()].getInsets().left);
+            panel.setBounds(rightX - width, insets.top + h1, width, maxHeight);
+          }
+          else if (dir == Direction.LEFT) {
+            int x = insets.left + w1 - windowBars[dir.getValue()].getInsets().right;
+            int width = Math.min(panel.getPreferredSize().width,
+                maxWidth + windowBars[dir.getValue()].getInsets().right);
+            panel.setBounds(x, insets.top + h1, width, maxHeight);
+          }
+          else if (dir == Direction.DOWN) {
+            int bottomY = parent.getHeight() - insets.bottom - h2 + windowBars[dir.getValue()].getInsets().top;
+            int height = Math.min(panel.getPreferredSize().height,
+                maxHeight + windowBars[dir.getValue()].getInsets().top);
+            panel.setBounds(insets.left + w1, bottomY - height, maxWidth, height);
+          }
+          else {
+            int y = insets.top + h1 - windowBars[dir.getValue()].getInsets().bottom;
+            int height = Math.min(panel.getPreferredSize().height,
+                maxHeight + windowBars[dir.getValue()].getInsets().bottom);
+            panel.setBounds(insets.left + w1, y, maxWidth, height);
+          }
+        }
+      }
+    }
+
+    public Dimension minimumLayoutSize(Container parent) {
+      return LayoutUtil.add(mainPanel.getMinimumSize(), parent.getInsets());
+    }
+
+    public Dimension preferredLayoutSize(Container parent) {
+      return LayoutUtil.add(mainPanel.getPreferredSize(), parent.getInsets());
+    }
+
+    public void removeLayoutComponent(Component comp) {
+    }
+
+  }
+
+  private final ShapedPanel layeredPane = new ShapedPanel() {
+    public boolean isOptimizedDrawingEnabled() {
+      return false;
+    }
+  };
+
+  private final ShapedPanel windowPanel = new ShapedPanel(new StretchLayout(true, true));
+
+  private final SimplePanel mainPanel = new SimplePanel();
+
+  private JFrame dummyFrame;
+
+  private final ViewSerializer viewSerializer;
+  private DockingWindow window;
+  //  private View lastFocusedView;
+  private final WindowBar[] windowBars = new WindowBar[Direction.getDirections().length];
+  private final ArrayList floatingWindows = new ArrayList();
+  private DockingWindow maximizedWindow;
+  private View focusedView;
+  private ArrayList lastFocusedWindows = new ArrayList(4);
+  private ArrayList focusedWindows = new ArrayList(4);
+  private final ArrayList views = new ArrayList();
+  private boolean cleanUpModel;
+  private final Runnable modelCleanUpEvent = new Runnable() {
+    public void run() {
+      if (cleanUpModel) {
+        cleanUpModel = false;
+        getWindowItem().cleanUp();
+      }
+    }
+  };
+
+  private final JLabel dragTextLabel = new JLabel();
+  private Container dragTextContainer;
+
+  private DragLabelWindow dragTextWindow;
+
+  private final Component dragRectangle;
+  private JRootPane currentDragRootPane;
+
+  /**
+   * Creates an empty root window.
+   *
+   * @param viewSerializer used when reading and writing views
+   * @since IDW 1.1.0
+   */
+  public RootWindow(ViewSerializer viewSerializer) {
+    this(false, viewSerializer);
+  }
+
+  /**
+   * Creates an empty root window with support for heavyweight components inside the
+   * views.
+   *
+   * @param heavyweightSupport true for heavy weight component support, otherwise false
+   * @param viewSerializer     used when reading and writing views
+   * @since IDW 1.4.0
+   */
+  public RootWindow(boolean heavyweightSupport, ViewSerializer viewSerializer) {
+    super(new RootWindowItem());
+    this.heavyweightSupport = heavyweightSupport;
+
+    dragRectangle = heavyweightSupport ? new HeavyWeightDragRectangle() : (Component) new ShapedPanel();
+
+    getWindowProperties().addSuperObject(getRootWindowProperties().getDockingWindowProperties());
+
+    mainPanel.setLayout(new BorderLayout2());
+    mainPanel.add(windowPanel, new Point(1, 1));
+
+    createWindowBars();
+
+    layeredPane.add(mainPanel);
+    layeredPane.setLayout(new SingleComponentLayout());
+    setComponent(layeredPane);
+
+    this.viewSerializer = viewSerializer;
+
+    dragTextLabel.setOpaque(true);
+
+    if (heavyweightSupport) {
+      dragTextContainer = new HeavyWeightContainer(dragTextLabel, true);
+      dragTextContainer.validate();
+    }
+    else
+      dragTextContainer = dragTextLabel;
+
+    init();
+    FocusManager.getInstance();
+
+    addTabMouseButtonListener(new MouseButtonListener() {
+      public void mouseButtonEvent(MouseEvent event) {
+        if (event.isConsumed())
+          return;
+
+        DockingWindow window = (DockingWindow) event.getSource();
+
+        if (event.getID() == MouseEvent.MOUSE_PRESSED &&
+            event.getButton() == MouseEvent.BUTTON1 &&
+            !event.isShiftDown() &&
+            window.isShowing()) {
+          RestoreFocusWindowAction.INSTANCE.perform(window);
+        }
+        else if (event.getID() == MouseEvent.MOUSE_CLICKED && event.getButton() == MouseEvent.BUTTON1) {
+          if (event.getClickCount() == 2) {
+            if ((window.getWindowParent() instanceof WindowBar) && getRootWindowProperties()
+                .getDoubleClickRestoresWindow())
+              RestoreWithAbortWindowAction.INSTANCE.perform(window);
+            else {
+              new StateDependentWindowAction(MaximizeWithAbortWindowAction.INSTANCE,
+                  NullWindowAction.INSTANCE,
+                  RestoreParentWithAbortWindowAction.INSTANCE).perform(window);
+            }
+          }
+        }
+      }
+    });
+  }
+
+  /**
+   * Creates a root window with the given window as window inside this root window.
+   *
+   * @param viewSerializer used when reading and writing views
+   * @param window         the window that is placed inside the root window
+   */
+  public RootWindow(ViewSerializer viewSerializer, DockingWindow window) {
+    this(false, viewSerializer, window);
+  }
+
+  /**
+   * Creates a root window with support for heavyweight components inside the views and the
+   * given window inside as window inside this root window.
+   *
+   * @param heavyweightSupport true for heavy weight component support, otherwise false
+   * @param viewSerializer     used when reading and writing views
+   * @param window             the window that is placed inside the root window
+   * @since IDW 1.4.0
+   */
+  public RootWindow(boolean heavyweightSupport, ViewSerializer viewSerializer, DockingWindow window) {
+    this(heavyweightSupport, viewSerializer);
+    setWindow(window);
+  }
+
+  /**
+   * Returns the view that currently contains the focus.
+   *
+   * @return The currently focused view, null if no view has focus
+   */
+  public View getFocusedView() {
+    return focusedView;
+  }
+
+  void addFocusedWindow(DockingWindow window) {
+    for (int i = 0; i < lastFocusedWindows.size(); i++) {
+      if (((WeakReference) lastFocusedWindows.get(i)).get() == window)
+        return;
+    }
+
+    lastFocusedWindows.add(new WeakReference(window));
+  }
+
+  void setFocusedView(View view) {
+    if (view == focusedView)
+      return;
+
+    //    System.out.println(focusedView + " -> " + view);
+
+    View previouslyFocusedView = focusedView;
+    focusedView = view;
+
+    for (DockingWindow w = view; w != null; w = w.getWindowParent()) {
+      focusedWindows.add(new WeakReference(w));
+
+      for (int i = 0; i < lastFocusedWindows.size(); i++) {
+        if (((WeakReference) lastFocusedWindows.get(i)).get() == w) {
+          lastFocusedWindows.remove(i);
+          break;
+        }
+      }
+    }
+
+    for (int i = 0; i < lastFocusedWindows.size(); i++) {
+      DockingWindow w = (DockingWindow) ((WeakReference) lastFocusedWindows.get(i)).get();
+
+      if (w != null) {
+        w.setFocused(false);
+      }
+    }
+
+    ArrayList temp = lastFocusedWindows;
+    lastFocusedWindows = focusedWindows;
+    focusedWindows = temp;
+
+    for (int i = 0; i < lastFocusedWindows.size(); i++) {
+      DockingWindow w = (DockingWindow) ((WeakReference) lastFocusedWindows.get(i)).get();
+
+      if (w != null)
+        w.setFocused(true);
+    }
+
+    if (view != null) {
+      view.childGainedFocus(null, view);
+    }
+    else {
+      clearFocus(null);
+    }
+
+    // Notify windows that are not ancestors of the new focused view
+    for (int i = 0; i < focusedWindows.size(); i++) {
+      DockingWindow w = (DockingWindow) ((WeakReference) focusedWindows.get(i)).get();
+
+      if (w != null) {
+        w.fireViewFocusChanged(previouslyFocusedView, focusedView);
+      }
+    }
+
+    // Notify windows that are ancestors of the new focused view
+    for (int i = 0; i < lastFocusedWindows.size(); i++) {
+      DockingWindow w = (DockingWindow) ((WeakReference) lastFocusedWindows.get(i)).get();
+
+      if (w != null) {
+        w.fireViewFocusChanged(previouslyFocusedView, focusedView);
+      }
+    }
+
+    focusedWindows.clear();
+  }
+
+  /**
+   * Returns the property values for this root window. The property values will be inherited to docking windows inside
+   * this root window.
+   *
+   * @return the property values for this root window
+   */
+  public RootWindowProperties getRootWindowProperties() {
+    return ((RootWindowItem) getWindowItem()).getRootWindowProperties();
+  }
+
+  /**
+   * Returns the direction of the closest enabled window bar to a docking window. The distance is measured from the
+   * window edge that is furthest away from the bar.
+   *
+   * @param window the docking window
+   * @return the direction of the closest enabled window bar to a docking window
+   */
+  public Direction getClosestWindowBar(DockingWindow window) {
+    Point pos = SwingUtilities.convertPoint(window.getParent(), window.getLocation(), this);
+
+    int[] distances = new int[]{
+                                getWindowBar(Direction.UP).isEnabled() ? pos.y + window.getHeight() : Integer.MAX_VALUE,
+                                                                       getWindowBar(Direction.DOWN).isEnabled() ? getHeight() - pos.y : Integer.MAX_VALUE,
+                                                                                                                getWindowBar(Direction.LEFT).isEnabled() ? pos.x + window.getWidth() : Integer.MAX_VALUE,
+                                                                                                                                                         getWindowBar(Direction.RIGHT).isEnabled() ? getWidth() - pos.x : Integer.MAX_VALUE};
+
+    Direction dir =
+      new Direction[]{Direction.UP, Direction.DOWN, Direction.LEFT, Direction.RIGHT}[ArrayUtil.findSmallest(
+          distances)];
+    return getWindowBar(dir).isEnabled() ? dir : null;
+  }
+
+  /**
+   * Returns the window bar in the direction.
+   *
+   * @param direction the direction
+   * @return the window bar in the direction
+   */
+  public WindowBar getWindowBar(Direction direction) {
+    return windowBars[direction.getValue()];
+  }
+
+  /**
+   * Sets the top level docking window inside this root window.
+   *
+   * @param newWindow the top level docking window
+   */
+  public void setWindow(DockingWindow newWindow) {
+    if (window == newWindow)
+      return;
+
+    if (window == null) {
+      DockingWindow actualWindow = addWindow(newWindow);
+      doReplace(null, actualWindow);
+
+      if (getUpdateModel() && actualWindow.getWindowItem().getRootItem() != getWindowItem()) {
+        getWindowItem().removeAll();
+        addWindowItem(actualWindow, -1);
+      }
+    }
+    else if (newWindow == null) {
+      removeChildWindow(window);
+      window = null;
+    }
+    else
+      replaceChildWindow(window, newWindow);
+  }
+
+  /**
+   * Returns the top level docking window inside this root window.
+   *
+   * @return the top level docking window inside this root window
+   */
+  public DockingWindow getWindow() {
+    return window;
+  }
+
+  /**
+   * <p>
+   * Creates and shows a floating window with the given window as top-level window in the floating window or without
+   * any top-level window i.e. empty floating window.
+   * </p>
+   *
+   * <p>
+   * <strong>Note 1:</strong> The created floating window is not visible per default. To make it visible, call
+   * {@link FloatingWindow}.getTopLevelAncestor().setVisible(true);
+   * </p>
+   *
+   * <p>
+   * <strong>Note 2:</strong> Floating windows are dynamically created when a window is undocked and closed/removed
+   * when all windows inside the floating window has been removed (i.e. cloased/docked/undocked to another floating
+   * window) from the floating window. The root window has a refernce to the floating window as long as the floating
+   * window has windows inside it i.e. it is not necessary to keep references to the floating window because the root
+   * window will handle this.
+   * </p>
+   *
+   * @param location  the floating window's location on the screen
+   * @param innerSize the inner dimension of the floating window's top level container i.e.the size of the root pane
+   * @param window    the docking window that is the top level window in this floating window or null for no top-level
+   *                  window i.e. empty floating window
+   * @return the floating window
+   * @since IDW 1.4.0
+   */
+  public FloatingWindow createFloatingWindow(Point location, Dimension innerSize, DockingWindow window) {
+    FloatingWindow fw = new FloatingWindow(this, window, location, innerSize);
+    floatingWindows.add(fw);
+    addWindow(fw);
+    return fw;
+  }
+
+  FloatingWindow createFloatingWindow() {
+    FloatingWindow fw = new FloatingWindow(this);
+    floatingWindows.add(fw);
+    addWindow(fw);
+    return fw;
+  }
+
+  FloatingWindow createFloatingWindow(DockingWindow window, Point p) {
+    Dimension size = window.getSize();
+    if (size.width <= 0 || size.height <= 0) {
+      Dimension preferred = window.getPreferredSize();
+      size =
+        new Dimension(Math.max(FLOATING_WINDOW_MIN_WIDTH, preferred.width),
+            Math.max(FLOATING_WINDOW_MIN_HEIGHT, preferred.height));
+    }
+
+    FloatingWindow fw = createFloatingWindow(p, size, window);
+    fw.getTopLevelAncestor().setVisible(true);
+
+    return fw;
+  }
+
+  void removeFloatingWindow(FloatingWindow fw) {
+    floatingWindows.remove(fw);
+    removeWindow(fw);
+  }
+
+  /**
+   * Returns the view serializer object for the views inside this root window.
+   *
+   * @return the view serializer object for the views inside this root window
+   */
+  public ViewSerializer getViewSerializer() {
+    return viewSerializer;
+  }
+
+  public DockingWindow getChildWindow(int index) {
+    return index < 4 ? windowBars[index] :
+      window != null ? (index == 4 ? window : (DockingWindow) floatingWindows.get(index - 5)) :
+        (DockingWindow) floatingWindows.get(index - 4);
+  }
+
+  public int getChildWindowCount() {
+    return 4 + (window == null ? 0 : 1) + floatingWindows.size();
+  }
+
+  public Icon getIcon() {
+    return null;
+  }
+
+  /**
+   * Writes the state of this root window and all child windows.
+   *
+   * @param out the stream on which to write the state
+   * @throws IOException if there is a stream error
+   */
+  public void write(ObjectOutputStream out) throws IOException {
+    write(out, true);
+  }
+
+  /**
+   * Writes the state of this root window and all child windows.
+   *
+   * @param out             the stream on which to write the state
+   * @param writeProperties true if the property values for all docking windows should be written to the stream
+   * @throws IOException if there is a stream error
+   */
+  public void write(ObjectOutputStream out, boolean writeProperties) throws IOException {
+    cleanUpModel();
+    out.writeInt(SERIALIZE_VERSION);
+    out.writeBoolean(writeProperties);
+    WriteContext context = new WriteContext(writeProperties, getViewSerializer());
+
+    final ArrayList v = new ArrayList();
+
+    for (int i = 0; i < views.size(); i++) {
+      View view = (View) ((WeakReference) views.get(i)).get();
+
+      if (view != null)
+        v.add(view);
+    }
+
+    writeViews(v, out, context);
+    ViewWriter viewWriter = new ViewWriter() {
+      public void writeWindowItem(WindowItem windowItem, ObjectOutputStream out, WriteContext context) throws
+      IOException {
+        if (windowItem.getRootItem() == getWindowItem()) {
+          out.writeBoolean(true);
+          writeWindowItemIndex(windowItem, out);
+          out.writeInt(-1);
+        }
+        else {
+          out.writeBoolean(false);
+          windowItem.writeSettings(out, context);
+        }
+      }
+
+      public void writeView(View view, ObjectOutputStream out, WriteContext context) throws IOException {
+        for (int i = 0; i < v.size(); i++) {
+          if (v.get(i) == view) {
+            out.writeInt(i);
+            return;
+          }
+        }
+
+        out.writeInt(-1);
+      }
+    };
+    getWindowItem().write(out, context, viewWriter);
+
+    for (int i = 0; i < 4; i++)
+      windowBars[i].write(out, context, viewWriter);
+
+    out.writeInt(floatingWindows.size());
+
+    for (int i = 0; i < floatingWindows.size(); i++)
+      ((FloatingWindow) floatingWindows.get(i)).write(out, context, viewWriter);
+
+    writeLocations(out);
+
+    if (maximizedWindow != null)
+      writeMaximized(maximizedWindow, out);
+
+    out.writeInt(-1);
+  }
+
+  private void writeWindowItemIndex(WindowItem item, ObjectOutputStream out) throws IOException {
+    if (item.getParent() == null)
+      return;
+
+    writeWindowItemIndex(item.getParent(), out);
+    int index = item.getParent().getWindowIndex(item);
+    out.writeInt(index);
+  }
+
+  private void writeMaximized(DockingWindow window, ObjectOutputStream out) throws IOException {
+    DockingWindow parent = window.getWindowParent();
+
+    if (parent != null) {
+      writeMaximized(parent, out);
+      out.writeInt(parent.getChildWindowIndex(window));
+    }
+  }
+
+  private static void writeViews(ArrayList views, ObjectOutputStream out, WriteContext context) throws IOException {
+    out.writeInt(views.size());
+
+    for (int i = 0; i < views.size(); i++)
+      ((View) views.get(i)).write(out, context);
+  }
+
+  /**
+   * Reads a previously written window state. This will create child windows and read their state.
+   *
+   * @param in the stream from which to read the state
+   * @throws IOException if there is a stream error
+   */
+  public void read(ObjectInputStream in) throws IOException {
+    read(in, true);
+  }
+
+  private void oldInternalRead(ObjectInputStream in, ReadContext context) throws IOException {
+    setWindow(in.readBoolean() ? WindowDecoder.decodeWindow(in, context) : null);
+
+    for (int i = 0; i < 4; i++) {
+      in.readInt(); // DockingWindow bar ID
+      windowBars[i].oldRead(in, context);
+    }
+
+    super.oldRead(in, context);
+    readLocations(in, this, context.getVersion());
+
+    if (context.getVersion() > 1) {
+      int viewCount = in.readInt();
+
+      for (int i = 0; i < viewCount; i++) {
+        View view = (View) WindowDecoder.decodeWindow(in, context);
+        view.setRootWindow(this);
+        view.readLocations(in, this, context.getVersion());
+      }
+    }
+  }
+
+  private void newInternalRead(ObjectInputStream in, ReadContext context) throws IOException {
+    beginUpdateModel();
+
+    try {
+      int viewCount = in.readInt();
+      final View[] views = new View[viewCount];
+
+      for (int i = 0; i < viewCount; i++) {
+        views[i] = View.read(in, context);
+
+        if (views[i] != null)
+          views[i].setRootWindow(this);
+      }
+
+      ViewReader viewReader = new ViewReader() {
+        public ViewItem readViewItem(ObjectInputStream in, ReadContext context) throws IOException {
+          View view = readView(in, context);
+          return view == null ? new ViewItem() : (ViewItem) view.getWindowItem();
+        }
+
+        public WindowItem readWindowItem(ObjectInputStream in, ReadContext context) throws IOException {
+          if (in.readBoolean()) {
+            int index;
+            WindowItem item = getWindowItem();
+
+            while ((index = in.readInt()) != -1) {
+              item = item.getWindow(index);
+            }
+
+            return item;
+          }
+          else
+            return null;
+        }
+
+        public TabWindow createTabWindow(DockingWindow[] childWindows, TabWindowItem windowItem) {
+          TabWindow tabWindow = new TabWindow(childWindows, windowItem);
+          tabWindow.updateSelectedTab();
+          return tabWindow;
+        }
+
+        public SplitWindow createSplitWindow(DockingWindow leftWindow,
+                                             DockingWindow rightWindow,
+                                             SplitWindowItem windowItem) {
+          return new SplitWindow(windowItem.isHorizontal(),
+              windowItem.getDividerLocation(),
+              leftWindow,
+              rightWindow,
+              windowItem);
+        }
+
+        public View readView(ObjectInputStream in, ReadContext context) throws IOException {
+          int id = in.readInt();
+          return id == -1 ? null : (View) views[id];
+        }
+      };
+      setWindow(getWindowItem().read(in, context, viewReader));
+
+      for (int i = 0; i < 4; i++)
+        windowBars[i].newRead(in, context, viewReader);
+
+      if (context.getVersion() >= 4) {
+        int count = in.readInt();
+
+        for (int i = 0; i < count; i++) {
+          FloatingWindow w = createFloatingWindow();
+          w.read(in, context, viewReader);
+        }
+
+      }
+
+      readLocations(in, this, context.getVersion());
+    }
+    finally {
+      endUpdateModel();
+    }
+  }
+
+  /**
+   * Reads a previously written window state. This will create child windows and read their state.
+   *
+   * @param in             the stream from which to read the state
+   * @param readProperties true if the property values for all child windows should be read. This parameter can be set
+   *                       to true or false regardless of if the property values was included when the state was
+   *                       written, though obviously no property values are read if there aren't any in the stream.
+   * @throws IOException if there is a stream error
+   */
+  public void read(ObjectInputStream in, boolean readProperties) throws IOException {
+    FocusManager.getInstance().startIgnoreFocusChanges();
+    PropertyMapManager.getInstance().beginBatch();
+
+    try {
+      setWindow(null);
+
+      while (floatingWindows.size() > 0) {
+        ((FloatingWindow) floatingWindows.get(0)).close();
+      }
+
+      int serializeVersion = in.readInt();
+
+      if (serializeVersion > SERIALIZE_VERSION)
+        throw new IOException(
+        "Can't read serialized data because it was written by a later version of InfoNode Docking Windows!");
+
+      ReadContext context = new ReadContext(this, serializeVersion, in.readBoolean(), readProperties);
+
+      if (context.getVersion() < 3)
+        oldInternalRead(in, context);
+      else
+        newInternalRead(in, context);
+
+      if (serializeVersion > 1)
+        readMaximized(in);
+
+      FocusManager.focusWindow(this);
+    }
+    finally {
+      PropertyMapManager.getInstance().endBatch();
+      SwingUtilities.invokeLater(new Runnable() {
+        public void run() {
+          FocusManager.getInstance().stopIgnoreFocusChanges();
+        }
+      });
+    }
+  }
+
+  private void readMaximized(ObjectInputStream in) throws IOException {
+    int index;
+    DockingWindow w = this;
+
+    while ((index = in.readInt()) != -1) {
+      if (index >= w.getChildWindowCount()) {
+        while (in.readInt() != -1) ;
+        return;
+      }
+
+      w = w.getChildWindow(index);
+    }
+
+    if (w != this)
+      setMaximizedWindow(w);
+  }
+
+  /**
+   * Returns the maximized window in this root window, or null if there no maximized window.
+   *
+   * @return the maximized window in this root window, or null if there no maximized window
+   * @since IDW 1.1.0
+   */
+  public DockingWindow getMaximizedWindow() {
+    return maximizedWindow;
+  }
+
+  /**
+   * Sets the maximized window in this root window.
+   * This method takes the window component and displays it at the top in the root window. It does NOT modify the
+   * window tree structure, ie the window parent remains the unchanged.
+   *
+   * @param window the maximized window in this root window, null means no maximized window
+   * @since IDW 1.1.0
+   */
+  public void setMaximizedWindow(DockingWindow window) {
+    /*if (window == maximizedWindow)
+      return;*/
+
+    if (window != null && (window.isMinimized() || window.isUndocked()))
+      return;
+
+    internalSetMaximizedWindow(window);
+  }
+
+  void addView(View view) {
+    int freeIndex = views.size();
+
+    for (int i = 0; i < views.size(); i++) {
+      View v = (View) ((WeakReference) views.get(i)).get();
+
+      if (v == view)
+        return;
+
+      if (v == null)
+        freeIndex = i;
+    }
+
+    views.add(freeIndex, new WeakReference(view));
+  }
+
+  /**
+   * Removes all internal references to a view. It's not possible to restore the view to the previous location
+   * after this method has been called.
+   *
+   * If the view is located in the window tree where this is the root nothing happens.
+   *
+   * @param view all internal references to this view are removed
+   * @since IDW 1.4.0
+   */
+  public void removeView(View view) {
+    if (view.getRootWindow() == this)
+      return;
+
+    if (focusedView == view)
+      focusedView = null;
+
+    removeWeak(view, views);
+    removeWeak(view, lastFocusedWindows);
+    removeWeak(view, focusedWindows);
+
+    getWindowItem().removeWindowRefs(view);
+  }
+
+  private void removeWeak(Object item, java.util.List l) {
+    for (int i = 0; i < l.size(); i++) {
+      Object o = ((WeakReference) l.get(i)).get();
+
+      if (o == item) {
+        l.remove(i);
+        return;
+      }
+    }
+  }
+
+  private void internalSetMaximizedWindow(DockingWindow window) {
+    if (window == maximizedWindow)
+      return;
+
+    DockingWindow focusWindow = null;
+
+    if (maximizedWindow != null) {
+      DockingWindow oldMaximized = maximizedWindow;
+      maximizedWindow = null;
+
+      if (oldMaximized.getWindowParent() != null)
+        oldMaximized.getWindowParent().restoreWindowComponent(oldMaximized);
+
+      if (oldMaximized != this.window)
+        windowPanel.remove(oldMaximized);
+
+      focusWindow = oldMaximized;
+      fireWindowRestored(oldMaximized);
+    }
+
+    maximizedWindow = window;
+
+    if (maximizedWindow != null) {
+      if (maximizedWindow.getWindowParent() != null)
+        maximizedWindow.getWindowParent().removeWindowComponent(maximizedWindow);
+
+      if (maximizedWindow != this.window) {
+        windowPanel.add(maximizedWindow);
+
+        if (this.window != null)
+          this.window.setVisible(false);
+      }
+
+      maximizedWindow.setVisible(true);
+
+      focusWindow = maximizedWindow;
+      fireWindowMaximized(maximizedWindow);
+    }
+    else if (this.window != null) {
+      this.window.setVisible(true);
+    }
+
+    if (focusWindow != null)
+      FocusManager.focusWindow(focusWindow);
+  }
+
+  private void createWindowBars() {
+    final Direction[] directions = Direction.getDirections();
+
+    for (int i = 0; i < directions.length; i++) {
+      windowBars[i] = new WindowBar(this, directions[i]);
+      windowBars[i].setEnabled(false);
+      addWindow(windowBars[i]);
+      layeredPane.add(windowBars[i].getEdgePanel());
+
+      mainPanel.add(windowBars[i],
+          new Point(directions[i] == Direction.LEFT ?
+                                                     0 :
+                                                       directions[i] == Direction.RIGHT ? 2 : 1,
+                                                                                        directions[i] == Direction.UP ?
+                                                                                                                       0 :
+                                                                                                                         directions[i] == Direction.DOWN ? 2 : 1));
+
+      windowBars[i].addPropertyChangeListener("enabled", new PropertyChangeListener() {
+        public void propertyChange(PropertyChangeEvent evt) {
+          updateButtonVisibility();
+        }
+      });
+    }
+  }
+
+  JComponent getLayeredPane() {
+    return layeredPane;
+  }
+
+  JComponent getWindowPanel() {
+    return windowPanel;
+  }
+
+  protected void showChildWindow(DockingWindow window) {
+    if (maximizedWindow != null && window == this.window)
+      setMaximizedWindow(null);
+
+    super.showChildWindow(window);
+  }
+
+  protected void update() {
+    RootWindowProperties properties = getRootWindowProperties();
+    properties.getComponentProperties().applyTo(layeredPane);
+    InternalPropertiesUtil.applyTo(properties.getShapedPanelProperties(), layeredPane);
+
+    properties.getWindowAreaProperties().applyTo(windowPanel);
+    InternalPropertiesUtil.applyTo(properties.getWindowAreaShapedPanelProperties(), windowPanel);
+
+    properties.getDragLabelProperties().applyTo(dragTextLabel);
+
+    if (!heavyweightSupport) {
+      ShapedPanel dragRectangle = (ShapedPanel) this.dragRectangle;
+      InternalPropertiesUtil.applyTo(properties.getDragRectangleShapedPanelProperties(), dragRectangle);
+
+      if (dragRectangle.getComponentPainter() == null)
+        dragRectangle.setComponentPainter(
+            new RectangleComponentPainter(Color.BLACK, properties.getDragRectangleBorderWidth()));
+    }
+    else {
+      HeavyWeightDragRectangle rectangle = (HeavyWeightDragRectangle) dragRectangle;
+      ComponentPainter painter = properties.getDragRectangleShapedPanelProperties().getComponentPainter();
+      Color color = painter != null ? painter.getColor(this) : null;
+      rectangle.setColor(color != null ? color : Color.BLACK);
+      rectangle.setBorderWidth(properties.getDragRectangleBorderWidth());
+    }
+  }
+
+  void internalStartDrag(JComponent component) {
+    currentDragRootPane = getRootPane();
+
+    FloatingWindow fwStartedDrag = DockingUtil.getFloatingWindowFor((DockingWindow) component);
+
+    for (int i = 0; i < floatingWindows.size(); i++) {
+      FloatingWindow fw = (FloatingWindow) floatingWindows.get(i);
+      fw.startDrag();
+      if (dummyFrame != null && fw != fwStartedDrag)
+        ((JDialog) (fw.getTopLevelAncestor())).toFront();
+    }
+
+    if (dummyFrame != null && fwStartedDrag != null)
+      ((JDialog) (fwStartedDrag.getTopLevelAncestor())).toFront();
+  }
+
+  void stopDrag() {
+    if (dragTextContainer.getParent() != null) {
+      if (!heavyweightSupport)
+        dragTextContainer.getParent().repaint(dragTextContainer.getX(),
+            dragTextContainer.getY(),
+            dragTextContainer.getWidth(),
+            dragTextContainer.getHeight());
+      dragTextContainer.getParent().remove(dragTextContainer);
+      //dragTextContainer.setVisible(false);
+    }
+
+    if (dragTextWindow != null) {
+      dragTextWindow.setVisible(false);
+      dragTextWindow.dispose();
+      dragTextWindow = null;
+    }
+
+    if (dragRectangle.getParent() != null) {
+      if (!heavyweightSupport)
+        dragRectangle.getParent().repaint(dragRectangle.getX(),
+            dragRectangle.getY(),
+            dragRectangle.getWidth(),
+            dragRectangle.getHeight());
+      dragRectangle.getParent().remove(dragRectangle);
+    }
+
+    CursorManager.resetGlobalCursor(getCurrentDragRootPane());
+    currentDragRootPane = null;
+
+    for (int i = 0; i < floatingWindows.size(); i++) {
+      ((FloatingWindow) floatingWindows.get(i)).stopDrag();
+    }
+  }
+
+  boolean floatingWindowsContainPoint(Point p) {
+    for (int i = 0; i < floatingWindows.size(); i++) {
+      FloatingWindow c = ((FloatingWindow) floatingWindows.get(i));
+
+      if (c.isShowing() && c.windowContainsPoint(SwingUtilities.convertPoint(getRootPane(), p, c)))
+        return true;
+    }
+
+    return false;
+  }
+
+  void setCurrentDragRootPane(JRootPane rootPane) {
+    //if (rootPane != currentDragRootPane) {
+    CursorManager.resetGlobalCursor(getCurrentDragRootPane());
+    currentDragRootPane = rootPane;
+    //}
+  }
+
+  JRootPane getCurrentDragRootPane() {
+    return currentDragRootPane == null ? getRootPane() : currentDragRootPane;
+  }
+
+  Component getTopLevelComponent() {
+    Component c = getTopLevelAncestor();
+
+    if (c instanceof JFrame || c instanceof JDialog)
+      return c;
+
+    if (dummyFrame == null) {
+      dummyFrame = new JFrame("");
+      dummyFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+    }
+
+    return dummyFrame;
+  }
+
+  boolean isHeavyweightSupported() {
+    return heavyweightSupport;
+  }
+
+  private static boolean reparent(Container parent, Component c) {
+    if (c.getParent() != parent) {
+      if (c.getParent() != null)
+        c.getParent().remove(c);
+
+      parent.add(c);
+      return true;
+    }
+
+    return false;
+  }
+
+  void setDragCursor(Cursor cursor) {
+    CursorManager.setGlobalCursor(getCurrentDragRootPane(), cursor);
+
+    if (dragTextWindow != null)
+      dragTextWindow.setCursor(cursor);
+  }
+
+  void setDragText(Point textPoint, String text) {
+    if (textPoint != null) {
+      JRootPane currentDragRootPane = getCurrentDragRootPane();
+
+      if (reparent(currentDragRootPane.getLayeredPane(), dragTextContainer))
+        currentDragRootPane.getLayeredPane().setLayer(dragTextContainer,
+            JLayeredPane.DRAG_LAYER.intValue() +
+            (heavyweightSupport ? -1 : 1));
+
+      dragTextLabel.setText(text);
+      dragTextContainer.setSize(dragTextContainer.getPreferredSize());
+      Point p2 = SwingUtilities.convertPoint(currentDragRootPane, textPoint, dragTextContainer.getParent());
+      int yLocationOffs = dragTextLabel.getInsets().bottom;
+      dragTextContainer.setLocation((int) (p2.getX() - dragTextContainer.getWidth() / 2),
+          (int) (p2.getY() - dragTextContainer.getHeight() + yLocationOffs));
+
+      // Drag text window
+      Point rp = SwingUtilities.convertPoint(currentDragRootPane, textPoint, getRootPane());
+
+      if (!getRootPane().contains(rp) && !floatingWindowsContainPoint(rp)) {
+        dragTextContainer.setVisible(false);
+
+        if (dragTextWindow == null) {
+          Component c = getTopLevelComponent();
+          if (c instanceof Frame)
+            dragTextWindow = new DragLabelWindow((Frame) c);
+          else if (c instanceof Dialog)
+            dragTextWindow = new DragLabelWindow((Dialog) c);
+
+          if (dragTextWindow != null) {
+            dragTextWindow.setFocusableWindowState(false);
+            dragTextWindow.setFocusable(false);
+            getRootWindowProperties().getDragLabelProperties().applyTo(dragTextWindow.getLabel());
+            //dragTextWindow.setSize(dragText.getSize());
+          }
+        }
+
+        if (dragTextWindow != null) {
+          dragTextWindow.getLabel().setText(text);
+          SwingUtilities.convertPointToScreen(textPoint, currentDragRootPane);
+          dragTextWindow.setLocation(textPoint.x - dragTextContainer.getWidth() / 2,
+              (textPoint.y - dragTextContainer.getHeight() + yLocationOffs));
+          dragTextWindow.setVisible(true);
+        }
+      }
+      else {
+        dragTextContainer.setVisible(true);
+        if (heavyweightSupport)
+          dragTextContainer.repaint();
+
+        if (dragTextWindow != null)
+          dragTextWindow.setVisible(false);
+      }
+    }
+    else if (dragTextContainer.getParent() != null) {
+      dragTextContainer.setVisible(false);
+
+      if (dragTextWindow != null)
+        dragTextWindow.setVisible(false);
+    }
+  }
+
+  void setDragRectangle(Rectangle rect) {
+    if (rect != null) {
+      if (reparent(getCurrentDragRootPane().getLayeredPane(), dragRectangle))
+        getCurrentDragRootPane().getLayeredPane().setLayer(dragRectangle,
+            JLayeredPane.DRAG_LAYER.intValue() +
+            (heavyweightSupport ? -1 : 0));
+
+      dragRectangle.setBounds(SwingUtilities.convertRectangle(this, rect, dragRectangle.getParent()));
+      dragRectangle.setVisible(true);
+    }
+    else if (dragRectangle.getParent() != null)
+      dragRectangle.setVisible(false);
+  }
+
+  protected void doReplace(DockingWindow oldWindow, DockingWindow newWindow) {
+    if (oldWindow == window) {
+      if (window != null) {
+        windowPanel.remove(window);
+        window.setVisible(true);
+      }
+
+      window = newWindow;
+
+      if (window != null) {
+        if (maximizedWindow != null)
+          window.setVisible(false);
+
+        windowPanel.add(window);
+        windowPanel.revalidate();
+        //revalidate();
+      }
+    }
+  }
+
+  protected void doRemoveWindow(DockingWindow window) {
+    if (window == this.window) {
+      windowPanel.remove(window);
+      this.window.setVisible(true);
+      this.window = null;
+    }
+
+    repaint();
+  }
+
+  public RootWindow getRootWindow() {
+    return this;
+  }
+
+  protected boolean acceptsSplitWith(DockingWindow window) {
+    return false;
+  }
+
+  protected DropAction doAcceptDrop(Point p, DockingWindow window) {
+    if (maximizedWindow != null) {
+      Point p2 = SwingUtilities.convertPoint(this, p, maximizedWindow);
+
+      if (maximizedWindow.contains(p2) &&
+          getChildDropFilter().acceptDrop(new ChildDropInfo(window, this, p, maximizedWindow))) {
+        DropAction da = maximizedWindow.acceptDrop(p2, window);
+
+        if (da != null)
+          return da;
+      }
+    }
+
+    return super.doAcceptDrop(p, window);
+  }
+
+  protected DropAction acceptInteriorDrop(Point p, DockingWindow window) {
+    if (this.window != null)
+      return null;
+
+    if (getInteriorDropFilter().acceptDrop(new InteriorDropInfo(window, this, p)))
+      return new DropAction() {
+      public void execute(DockingWindow window, MouseEvent mouseEvent) {
+        setWindow(window);
+      }
+    };
+
+    return null;
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return getRootWindowProperties().getMap();
+  }
+
+  protected PropertyMap createPropertyObject() {
+    return new RootWindowProperties().getMap();
+  }
+
+  boolean windowBarEnabled() {
+    for (int i = 0; i < windowBars.length; i++)
+      if (windowBars[i].isEnabled())
+        return true;
+
+    return false;
+  }
+
+  void removeWindowComponent(DockingWindow window) {
+  }
+
+  void restoreWindowComponent(DockingWindow window) {
+  }
+
+  protected void cleanUpModel() {
+    if (!cleanUpModel) {
+      cleanUpModel = true;
+      SwingUtilities.invokeLater(modelCleanUpEvent);
+    }
+  }
+
+  protected boolean isShowingInRootWindow() {
+    return true;
+  }
+
+  public void updateUI() {
+    super.updateUI();
+
+    if (floatingWindows != null) {
+      for (int i = 0; i < floatingWindows.size(); i++)
+        SwingUtilities.updateComponentTreeUI(((JComponent) floatingWindows.get(i)).getTopLevelAncestor());
+
+      for (int i = 0; i < views.size(); i++) {
+        View v = (View) ((WeakReference) views.get(i)).get();
+        if (v != null && v.getWindowParent() == null)
+          SwingUtilities.updateComponentTreeUI(v);
+      }
+    }
+  }
+
+  protected void paintComponent(final Graphics g) {
+    //((Graphics2D)g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+    super.paintComponent(g);
+  }
+}
diff --git a/src/net/infonode/docking/SplitWindow.java b/src/net/infonode/docking/SplitWindow.java
new file mode 100644
index 0000000..166dda0
--- /dev/null
+++ b/src/net/infonode/docking/SplitWindow.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SplitWindow.java,v 1.50 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking;
+
+import net.infonode.docking.drop.InteriorDropInfo;
+import net.infonode.docking.drop.SplitDropInfo;
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.WindowAncestors;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.internalutil.DropAction;
+import net.infonode.docking.model.SplitWindowItem;
+import net.infonode.docking.model.ViewReader;
+import net.infonode.docking.model.ViewWriter;
+import net.infonode.docking.properties.SplitWindowProperties;
+import net.infonode.gui.ComponentUtil;
+import net.infonode.gui.SimpleSplitPane;
+import net.infonode.gui.SimpleSplitPaneListener;
+import net.infonode.gui.panel.BaseContainerUtil;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * A window with a split pane that contains two child windows.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.50 $
+ */
+public class SplitWindow extends DockingWindow {
+  private SimpleSplitPane splitPane;
+  private DockingWindow leftWindow;
+  private DockingWindow rightWindow;
+
+  /**
+   * Creates a split window.
+   *
+   * @param horizontal true if the split is horizontal
+   */
+  public SplitWindow(boolean horizontal) {
+    this(horizontal, null, null);
+  }
+
+  /**
+   * Creates a split window with with the given child windows.
+   *
+   * @param horizontal  true if the split is horizontal
+   * @param leftWindow  the left/upper window
+   * @param rightWindow the right/lower window
+   */
+  public SplitWindow(boolean horizontal, DockingWindow leftWindow, DockingWindow rightWindow) {
+    this(horizontal, 0.5f, leftWindow, rightWindow);
+  }
+
+  /**
+   * Creates a split window with with the given child windows.
+   *
+   * @param horizontal      true if the split is horizontal
+   * @param dividerLocation the divider location, 0 - 1
+   * @param leftWindow      the left/upper window
+   * @param rightWindow     the right/lower window
+   */
+  public SplitWindow(boolean horizontal, float dividerLocation, DockingWindow leftWindow, DockingWindow rightWindow) {
+    this(horizontal, dividerLocation, leftWindow, rightWindow, null);
+  }
+
+  protected SplitWindow(boolean horizontal, float dividerLocation, DockingWindow leftWindow,
+                        DockingWindow rightWindow, SplitWindowItem windowItem) {
+    super(windowItem == null ? new SplitWindowItem() : windowItem);
+
+    splitPane = new SimpleSplitPane(horizontal);
+    BaseContainerUtil.setForcedOpaque(splitPane, false);
+    //splitPane.setForcedOpaque(false);
+    splitPane.addListener(new SimpleSplitPaneListener() {
+      public void dividerLocationChanged(SimpleSplitPane simpleSplitPane) {
+        ((SplitWindowItem) getWindowItem()).setDividerLocation(simpleSplitPane.getDividerLocation());
+      }
+    });
+    setComponent(splitPane);
+    setWindows(leftWindow, rightWindow);
+    setHorizontal(horizontal);
+    setDividerLocation(dividerLocation);
+    splitPane.getDividerPanel().addMouseListener(new MouseAdapter() {
+      public void mousePressed(MouseEvent e) {
+        if (e.isPopupTrigger()) {
+          showPopupMenu(e);
+        }
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        mousePressed(e);
+      }
+    });
+    init();
+  }
+
+  /**
+   * Returns the property values for this split window.
+   *
+   * @return the property values for this split window
+   */
+  public SplitWindowProperties getSplitWindowProperties() {
+    return ((SplitWindowItem) getWindowItem()).getSplitWindowProperties();
+  }
+
+  /**
+   * Returns the left/upper child window.
+   *
+   * @return the left/upper child window
+   */
+  public DockingWindow getLeftWindow() {
+    return leftWindow;
+  }
+
+  /**
+   * Returns the right/lower child window.
+   *
+   * @return the right/lower child window
+   */
+  public DockingWindow getRightWindow() {
+    return rightWindow;
+  }
+
+  /**
+   * Sets the divider location as a fraction of this split window's size.
+   *
+   * @param dividerLocation the divider location as a fraction of this split window's size
+   */
+  public void setDividerLocation(float dividerLocation) {
+    splitPane.setDividerLocation(dividerLocation);
+  }
+
+  /**
+   * Returns the divider location as a fraction of this split window's size.
+   *
+   * @return the divider location as a fraction of this split window's size
+   */
+  public float getDividerLocation() {
+    return splitPane.getDividerLocation();
+  }
+
+  /**
+   * Sets the child windows of this split window.
+   *
+   * @param leftWindow  the left/upper child window
+   * @param rightWindow the right/lower child window
+   */
+  public void setWindows(final DockingWindow leftWindow, final DockingWindow rightWindow) {
+    if (leftWindow == getLeftWindow() && rightWindow == getRightWindow())
+      return;
+
+    optimizeAfter(null, new Runnable() {
+      public void run() {
+        WindowAncestors leftAncestors = leftWindow.storeAncestors();
+        WindowAncestors rightAncestors = rightWindow.storeAncestors();
+
+        DockingWindow lw = leftWindow.getContentWindow(SplitWindow.this);
+        DockingWindow rw = rightWindow.getContentWindow(SplitWindow.this);
+        lw.detach();
+        rw.detach();
+
+        if (getLeftWindow() != null)
+          removeWindow(getLeftWindow());
+
+        if (getRightWindow() != null)
+          removeWindow(getRightWindow());
+
+        SplitWindow.this.leftWindow = lw;
+        SplitWindow.this.rightWindow = rw;
+        splitPane.setComponents(lw, rw);
+        addWindow(lw);
+        addWindow(rw);
+
+        if (getUpdateModel()) {
+          addWindowItem(getLeftWindow(), -1);
+          addWindowItem(getRightWindow(), -1);
+          cleanUpModel();
+        }
+
+        leftWindow.notifyListeners(leftAncestors);
+        rightWindow.notifyListeners(rightAncestors);
+      }
+    });
+  }
+
+  /**
+   * Returns true if this SplitWindow is a horizontal split, otherwise it's vertical.
+   *
+   * @return true if this SplitWindow is a horizontal split, otherwise it's vertical
+   * @since IDW 1.2.0
+   */
+  public boolean isHorizontal() {
+    return splitPane.isHorizontal();
+  }
+
+  /**
+   * Sets the split to horizontal or vertical.
+   *
+   * @param horizontal if true the split is set to horizontal, otherwise vertical
+   * @since IDW 1.2.0
+   */
+  public void setHorizontal(boolean horizontal) {
+    splitPane.setHorizontal(horizontal);
+    ((SplitWindowItem) getWindowItem()).setHorizontal(horizontal);
+  }
+
+  protected void update() {
+    splitPane.setDividerSize(getSplitWindowProperties().getDividerSize());
+    splitPane.setContinuousLayout(getSplitWindowProperties().getContinuousLayoutEnabled());
+    splitPane.setDividerDraggable(getSplitWindowProperties().getDividerLocationDragEnabled());
+    splitPane.setDragIndicatorColor(getSplitWindowProperties().getDragIndicatorColor());
+  }
+
+  protected void optimizeWindowLayout() {
+    DockingWindow parent = getWindowParent();
+
+    if (parent != null && (getRightWindow() == null || getLeftWindow() == null)) {
+      if (getRightWindow() == null && getLeftWindow() == null)
+        parent.removeChildWindow(this);
+      else {
+        DockingWindow w = getRightWindow() == null ? getLeftWindow() : getRightWindow();
+        parent.internalReplaceChildWindow(this, w.getBestFittedWindow(parent));
+      }
+    }
+  }
+
+  public DockingWindow getChildWindow(int index) {
+    return getWindows()[index];
+  }
+
+
+  protected void rootChanged(RootWindow oldRoot, RootWindow newRoot) {
+    super.rootChanged(oldRoot, newRoot);
+    if (newRoot != null)
+      splitPane.setHeavyWeightDragIndicator(newRoot.isHeavyweightSupported());
+  }
+
+  private DockingWindow[] getWindows() {
+    return getLeftWindow() == null ?
+           (getRightWindow() == null ? new DockingWindow[0] : new DockingWindow[]{getRightWindow()}) :
+           getRightWindow() == null ?
+           new DockingWindow[]{getLeftWindow()} :
+           new DockingWindow[]{getLeftWindow(), getRightWindow()};
+  }
+
+  public int getChildWindowCount() {
+    return getWindows().length;
+  }
+
+  public Icon getIcon() {
+    return getLeftWindow() == null ?
+           (getRightWindow() == null ? null : getRightWindow().getIcon()) : getLeftWindow().getIcon();
+  }
+
+  protected void doReplace(DockingWindow oldWindow, DockingWindow newWindow) {
+    if (getLeftWindow() == oldWindow) {
+      leftWindow = newWindow;
+      splitPane.setLeftComponent(newWindow);
+    }
+    else {
+      rightWindow = newWindow;
+      splitPane.setRightComponent(newWindow);
+    }
+
+    ComponentUtil.validate(splitPane);
+  }
+
+  protected void doRemoveWindow(DockingWindow window) {
+    if (window == getLeftWindow()) {
+      leftWindow = null;
+      splitPane.setLeftComponent(null);
+    }
+    else {
+      rightWindow = null;
+      splitPane.setRightComponent(null);
+    }
+  }
+
+  protected DockingWindow oldRead(ObjectInputStream in, ReadContext context) throws IOException {
+    splitPane.setHorizontal(in.readBoolean());
+    splitPane.setDividerLocation(in.readFloat());
+    DockingWindow leftWindow = WindowDecoder.decodeWindow(in, context);
+    DockingWindow rightWindow = WindowDecoder.decodeWindow(in, context);
+    super.oldRead(in, context);
+
+    if (leftWindow != null && rightWindow != null) {
+      setWindows(leftWindow, rightWindow);
+      return this;
+    }
+    else
+      return leftWindow != null ? leftWindow : rightWindow != null ? rightWindow : null;
+  }
+
+
+  protected void updateWindowItem(RootWindow rootWindow) {
+    super.updateWindowItem(rootWindow);
+    ((SplitWindowItem) getWindowItem()).setParentSplitWindowProperties(rootWindow == null ?
+                                                                       SplitWindowItem.emptyProperties :
+                                                                       rootWindow.getRootWindowProperties()
+                                                                           .getSplitWindowProperties());
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return getSplitWindowProperties().getMap();
+  }
+
+  protected PropertyMap createPropertyObject() {
+    return new SplitWindowProperties().getMap();
+  }
+
+  void removeWindowComponent(DockingWindow window) {
+    if (window == getLeftWindow())
+      splitPane.setLeftComponent(null);
+    else
+      splitPane.setRightComponent(null);
+  }
+
+  void restoreWindowComponent(DockingWindow window) {
+    if (window == getLeftWindow())
+      splitPane.setLeftComponent(leftWindow);
+    else
+      splitPane.setRightComponent(rightWindow);
+  }
+
+  protected int getChildEdgeDepth(DockingWindow window, Direction dir) {
+    return (window == leftWindow ? dir : dir.getOpposite()) == (isHorizontal() ? Direction.RIGHT : Direction.DOWN) ? 0 :
+           super.getChildEdgeDepth(window, dir);
+  }
+
+  protected DropAction doAcceptDrop(Point p, DockingWindow window) {
+    DropAction da = acceptChildDrop(p, window);
+
+    if (da != null)
+      return da;
+
+    float f = isHorizontal() ? (float) p.y / getHeight() : (float) p.x / getWidth();
+
+    if (f <= 0.33f) {
+      Direction splitDir = isHorizontal() ? Direction.UP : Direction.LEFT;
+      return getSplitDropFilter().acceptDrop(new SplitDropInfo(window, this, p, splitDir)) ?
+             split(window, splitDir) : null;
+    }
+    else if (f >= 0.66f) {
+      Direction splitDir = isHorizontal() ? Direction.DOWN : Direction.RIGHT;
+      return getSplitDropFilter().acceptDrop(new SplitDropInfo(window, this, p, splitDir)) ?
+             split(window, splitDir) : null;
+    }
+    else {
+      return getInteriorDropFilter().acceptDrop(new InteriorDropInfo(window, this, p)) ?
+             createTabWindow(window) : null;
+    }
+  }
+
+  protected void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+    out.writeInt(WindowIds.SPLIT);
+    viewWriter.writeWindowItem(getWindowItem(), out, context);
+    getLeftWindow().write(out, context, viewWriter);
+    getRightWindow().write(out, context, viewWriter);
+  }
+
+  protected DockingWindow newRead(ObjectInputStream in, ReadContext context, ViewReader viewReader) throws IOException {
+    DockingWindow leftWindow = WindowDecoder.decodeWindow(in, context, viewReader);
+    DockingWindow rightWindow = WindowDecoder.decodeWindow(in, context, viewReader);
+
+    if (leftWindow != null && rightWindow != null) {
+      setWindows(leftWindow, rightWindow);
+      return this;
+    }
+    else
+      return leftWindow != null ? leftWindow : rightWindow != null ? rightWindow : null;
+  }
+
+}
diff --git a/src/net/infonode/docking/TabWindow.java b/src/net/infonode/docking/TabWindow.java
new file mode 100644
index 0000000..8c19f26
--- /dev/null
+++ b/src/net/infonode/docking/TabWindow.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabWindow.java,v 1.57 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking;
+
+import net.infonode.docking.drag.DockingWindowDragSource;
+import net.infonode.docking.drag.DockingWindowDragger;
+import net.infonode.docking.drag.DockingWindowDraggerProvider;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.internalutil.*;
+import net.infonode.docking.model.TabWindowItem;
+import net.infonode.docking.model.ViewWriter;
+import net.infonode.docking.properties.TabWindowProperties;
+import net.infonode.properties.base.Property;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.properties.propertymap.PropertyMapTreeListener;
+import net.infonode.properties.propertymap.PropertyMapWeakListenerManager;
+import net.infonode.properties.util.PropertyChangeListener;
+import net.infonode.tabbedpanel.TabAdapter;
+import net.infonode.tabbedpanel.TabEvent;
+import net.infonode.tabbedpanel.TabRemovedEvent;
+import net.infonode.util.ArrayUtil;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.util.Map;
+
+/**
+ * A docking window containing a tabbed panel.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.57 $
+ */
+public class TabWindow extends AbstractTabWindow {
+  private static final ButtonInfo[] buttonInfos = {
+      new UndockButtonInfo(TabWindowProperties.UNDOCK_BUTTON_PROPERTIES),
+      new DockButtonInfo(TabWindowProperties.DOCK_BUTTON_PROPERTIES),
+      new MinimizeButtonInfo(TabWindowProperties.MINIMIZE_BUTTON_PROPERTIES),
+      new MaximizeButtonInfo(TabWindowProperties.MAXIMIZE_BUTTON_PROPERTIES),
+      new RestoreButtonInfo(TabWindowProperties.RESTORE_BUTTON_PROPERTIES),
+      new CloseButtonInfo(TabWindowProperties.CLOSE_BUTTON_PROPERTIES)
+  };
+
+  private AbstractButton[] buttons = new AbstractButton[buttonInfos.length];
+
+  private PropertyChangeListener minimumSizePropertiesListener = new PropertyChangeListener() {
+    public void propertyChanged(Property property, Object valueContainer, Object oldValue, Object newValue) {
+      revalidate();
+    }
+  };
+  private PropertyMapTreeListener buttonFactoryListener = new PropertyMapTreeListener() {
+    public void propertyValuesChanged(Map changes) {
+      doUpdateButtonVisibility(changes);
+    }
+  };
+
+  /**
+   * Creates an empty tab window.
+   */
+  public TabWindow() {
+    this((DockingWindow) null);
+  }
+
+  /**
+   * Creates a tab window with a tab containing the child window.
+   *
+   * @param window the child window
+   */
+  public TabWindow(DockingWindow window) {
+    this(window == null ? null : new DockingWindow[]{window});
+  }
+
+  /**
+   * Creates a tab window with tabs for the child windows.
+   *
+   * @param windows the child windows
+   */
+  public TabWindow(DockingWindow[] windows) {
+    this(windows, null);
+  }
+
+  protected TabWindow(DockingWindow[] windows, TabWindowItem windowItem) {
+    super(true, windowItem == null ? new TabWindowItem() : windowItem);
+
+    setTabWindowProperties(((TabWindowItem) getWindowItem()).getTabWindowProperties());
+
+    PropertyMapWeakListenerManager.addWeakPropertyChangeListener(getTabWindowProperties().getMap(),
+                                                                 TabWindowProperties.RESPECT_CHILD_WINDOW_MINIMUM_SIZE,
+                                                                 minimumSizePropertiesListener);
+
+
+    new DockingWindowDragSource(getTabbedPanel(), new DockingWindowDraggerProvider() {
+      public DockingWindowDragger getDragger(MouseEvent mouseEvent) {
+        if (!getWindowProperties().getDragEnabled())
+          return null;
+
+        Point p = SwingUtilities.convertPoint((Component) mouseEvent.getSource(),
+                                              mouseEvent.getPoint(),
+                                              getTabbedPanel());
+
+        return getTabbedPanel().tabAreaContainsPoint(p) ?
+               (getChildWindowCount() == 1 ? getChildWindow(0) : TabWindow.this).startDrag(getRootWindow()) :
+               null;
+      }
+    });
+
+    initMouseListener();
+    init();
+
+    getTabbedPanel().addTabListener(new TabAdapter() {
+      public void tabAdded(TabEvent event) {
+        doUpdateButtonVisibility(null);
+      }
+
+      public void tabRemoved(TabRemovedEvent event) {
+        doUpdateButtonVisibility(null);
+      }
+    });
+
+    if (windows != null) {
+      for (int i = 0; i < windows.length; i++)
+        addTab(windows[i]);
+    }
+
+    PropertyMapWeakListenerManager.addWeakTreeListener(getTabWindowProperties().getMap(), buttonFactoryListener);
+  }
+
+  public TabWindowProperties getTabWindowProperties() {
+    return ((TabWindowItem) getWindowItem()).getTabWindowProperties();
+  }
+
+  protected void tabSelected(WindowTab tab) {
+    super.tabSelected(tab);
+
+    if (getUpdateModel()) {
+      ((TabWindowItem) getWindowItem()).setSelectedItem(
+          tab == null ? null : getWindowItem().getChildWindowContaining(tab.getWindow().getWindowItem()));
+    }
+  }
+
+  protected void update() {
+  }
+
+  protected void updateButtonVisibility() {
+    doUpdateButtonVisibility(null);
+  }
+
+  private void doUpdateButtonVisibility(Map changes) {
+    //System.out.println("%%  Updating tab window buttons!");
+    //System.out.println(getTabbedPanel().getTabCount() + "  " + getTabbedPanel().getSelectedTab() + "  " + isMaximizable() + "  " + isUndockable() + "  " + isMinimizable() + "  " + isClosable());
+    if (InternalDockingUtil.updateButtons(buttonInfos,
+                                          buttons,
+                                          null,
+                                          this,
+                                          getTabWindowProperties().getMap(),
+                                          changes)) {
+      updateTabAreaComponents();
+    }
+
+    super.updateButtonVisibility();
+  }
+
+  protected int getTabAreaComponentCount() {
+    return ArrayUtil.countNotNull(buttons);
+  }
+
+  protected void getTabAreaComponents(int index, JComponent[] components) {
+    for (int i = 0; i < buttons.length; i++)
+      if (buttons[i] != null)
+        components[index++] = buttons[i];
+  }
+
+  protected void optimizeWindowLayout() {
+    if (getWindowParent() == null)
+      return;
+
+    if (getTabbedPanel().getTabCount() == 0)
+      internalClose();
+    else if (getTabbedPanel().getTabCount() == 1 &&
+             (getWindowParent().showsWindowTitle() || !getChildWindow(0).needsTitleWindow())) {
+      getWindowParent().internalReplaceChildWindow(this, getChildWindow(0).getBestFittedWindow(getWindowParent()));
+    }
+  }
+
+  public int addTab(DockingWindow w, int index) {
+    int actualIndex = super.addTab(w, index);
+    setSelectedTab(actualIndex);
+    return actualIndex;
+  }
+
+  protected int addTabNoSelect(DockingWindow window, int index) {
+    DockingWindow beforeWindow = index == getChildWindowCount() ? null : getChildWindow(index);
+
+    int i = super.addTabNoSelect(window, index);
+
+    if (getUpdateModel()) {
+      addWindowItem(window, beforeWindow == null ? -1 : getWindowItem().getWindowIndex(
+          getWindowItem().getChildWindowContaining(beforeWindow.getWindowItem())));
+    }
+
+    return i;
+  }
+
+  protected void updateWindowItem(RootWindow rootWindow) {
+    super.updateWindowItem(rootWindow);
+    ((TabWindowItem) getWindowItem()).setParentTabWindowProperties(rootWindow == null ?
+                                                                   TabWindowItem.emptyProperties :
+                                                                   rootWindow.getRootWindowProperties()
+                                                                       .getTabWindowProperties());
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return getTabWindowProperties().getMap();
+  }
+
+  protected PropertyMap createPropertyObject() {
+    return new TabWindowProperties().getMap();
+  }
+
+  protected int getEdgeDepth(Direction dir) {
+    return dir == getTabbedPanel().getProperties().getTabAreaOrientation() ?
+           1 :
+           super.getEdgeDepth(dir);
+  }
+
+  protected int getChildEdgeDepth(DockingWindow window, Direction dir) {
+    return dir == getTabbedPanel().getProperties().getTabAreaOrientation() ?
+           0 :
+           1 + super.getChildEdgeDepth(window, dir);
+  }
+
+  protected DockingWindow getOptimizedWindow() {
+    return getChildWindowCount() == 1 ? getChildWindow(0).getOptimizedWindow() : super.getOptimizedWindow();
+  }
+
+  protected boolean acceptsSplitWith(DockingWindow window) {
+    return super.acceptsSplitWith(window) && (getChildWindowCount() != 1 || getChildWindow(0) != window);
+  }
+
+  protected DockingWindow getBestFittedWindow(DockingWindow parentWindow) {
+    return getChildWindowCount() == 1 && (!getChildWindow(0).needsTitleWindow() || parentWindow.showsWindowTitle()) ?
+           getChildWindow(0).getBestFittedWindow(parentWindow) : this;
+  }
+
+  protected void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+    out.writeInt(WindowIds.TAB);
+    viewWriter.writeWindowItem(getWindowItem(), out, context);
+    super.write(out, context, viewWriter);
+  }
+
+
+}
diff --git a/src/net/infonode/docking/TabWindowHoverAction.java b/src/net/infonode/docking/TabWindowHoverAction.java
new file mode 100644
index 0000000..15a2be4
--- /dev/null
+++ b/src/net/infonode/docking/TabWindowHoverAction.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabWindowHoverAction.java,v 1.4 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking;
+
+import net.infonode.docking.properties.ViewTitleBarProperties;
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.tabbedpanel.*;
+import net.infonode.tabbedpanel.hover.TabbedPanelTitledTabHoverAction;
+import net.infonode.tabbedpanel.hover.TitledTabTabbedPanelHoverAction;
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+/**
+ * <p>
+ * TabWindowHoverAction is a hover action that makes it easy to change properties for a tab window
+ * and the title bar in the view.
+ * </p>
+ *
+ * <p>
+ * This action contains a titled tab proeprties object, a tabbed panel properties object and a view
+ * title bar properties object. Those objects are automatically added/removed as superobject to the
+ * currently hovered tab window if this action is set as a hover listener in the titled tab properties
+ * and the content panel properties for the tabbed panel.
+ * </p>
+ *
+ * <p>
+ * Example:
+ * <pre>
+ * rootWindowProperties.getTabWindowProperties().getTabbedPanelProperties().getContentPanelProperties().setHoverListener(tabWindowHoverAction);<br>
+ * rootWindowProperties.getTabWindowProperties().getTabProperties().getTitledTabProperties().setHoverListener(tabWindowHoverAction);
+ * </pre>
+ * </p>
+ *
+ * @author johan
+ * @since IDW 1.4.0
+ */
+public class TabWindowHoverAction implements HoverListener {
+  private TabbedPanelProperties tabbedPanelProperties;
+  private TitledTabProperties titledTabProperties;
+  private ViewTitleBarProperties viewTitleBarProperties;
+
+  private TabbedPanelTitledTabHoverAction tpTabAction;
+  private TitledTabTabbedPanelHoverAction tabTpAction;
+
+  private boolean titleBarPropsAdded = false;
+
+  private TabListener tabListener = new TabAdapter() {
+    public void tabSelected(TabStateChangedEvent event) {
+      if (event.getTab() != null) {
+        DockingWindow w = ((WindowTab) event.getTab()).getWindow();
+        if (!titleBarPropsAdded && w instanceof View)
+          addViewTitleBarProperties((View) w);
+      }
+    }
+
+    public void tabDeselected(TabStateChangedEvent event) {
+      if (event.getTab() != null) {
+        DockingWindow w = ((WindowTab) event.getTab()).getWindow();
+        if (titleBarPropsAdded && w instanceof View)
+          removeViewTitleBarProperties((View) w);
+      }
+    }
+  };
+
+  /**
+   * Creates an empty tab window hover action object.
+   */
+  public TabWindowHoverAction() {
+    this(new TabbedPanelProperties(), new TitledTabProperties(), new ViewTitleBarProperties());
+  }
+
+  public TabWindowHoverAction(TabbedPanelProperties tabbedPanelProperties,
+                              TitledTabProperties titledTabProperties,
+                              ViewTitleBarProperties viewTitleBarProperties) {
+    this.tabbedPanelProperties = tabbedPanelProperties;
+    this.titledTabProperties = titledTabProperties;
+    this.viewTitleBarProperties = viewTitleBarProperties;
+
+    tpTabAction = new TabbedPanelTitledTabHoverAction(tabbedPanelProperties, titledTabProperties);
+    tabTpAction = new TitledTabTabbedPanelHoverAction(titledTabProperties, tabbedPanelProperties);
+  }
+
+  /**
+   * Returns this action's tabbed panel properties
+   *
+   * @return tabbed panel properties
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return tabbedPanelProperties;
+  }
+
+  /**
+   * Returns this action's titled tab properties
+   *
+   * @return titled tab properties
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return titledTabProperties;
+  }
+
+  /**
+   * Returns this action's view title bar properties
+   *
+   * @return view title bar properties
+   */
+  public ViewTitleBarProperties getViewTitleBarProperties() {
+    return viewTitleBarProperties;
+  }
+
+  public void mouseEntered(HoverEvent event) {
+    if (event.getSource() instanceof TabbedPanel) {
+      TabbedPanel tp = (TabbedPanel) event.getSource();
+      tp.addTabListener(tabListener);
+      if (tp.getSelectedTab() != null) {
+        DockingWindow w = ((WindowTab) tp.getSelectedTab()).getWindow();
+        if (w instanceof View)
+          addViewTitleBarProperties((View) w);
+      }
+
+      tpTabAction.mouseEntered(event);
+    }
+    else if (event.getSource() instanceof TitledTab) {
+      WindowTab tab = (WindowTab) event.getSource();
+      tab.addTabListener(tabListener);
+      if (tab.isSelected() && tab.getWindow() instanceof View)
+        addViewTitleBarProperties((View) tab.getWindow());
+
+      tabTpAction.mouseEntered(event);
+    }
+  }
+
+  public void mouseExited(HoverEvent event) {
+    if (event.getSource() instanceof TabbedPanel) {
+      TabbedPanel tp = (TabbedPanel) event.getSource();
+      tp.removeTabListener(tabListener);
+      if (titleBarPropsAdded && tp.getSelectedTab() != null) {
+        DockingWindow w = ((WindowTab) tp.getSelectedTab()).getWindow();
+        if (w instanceof View)
+          removeViewTitleBarProperties((View) w);
+      }
+
+      tpTabAction.mouseExited(event);
+    }
+    else if (event.getSource() instanceof TitledTab) {
+      WindowTab tab = (WindowTab) event.getSource();
+      tab.removeTabListener(tabListener);
+      if (titleBarPropsAdded && tab.getWindow() instanceof View)
+        removeViewTitleBarProperties((View) tab.getWindow());
+
+      tabTpAction.mouseExited(event);
+    }
+  }
+
+  private void addViewTitleBarProperties(View view) {
+    view.getViewProperties().getViewTitleBarProperties().addSuperObject(viewTitleBarProperties);
+    titleBarPropsAdded = true;
+  }
+
+  private void removeViewTitleBarProperties(View view) {
+    view.getViewProperties().getViewTitleBarProperties().removeSuperObject(viewTitleBarProperties);
+    titleBarPropsAdded = false;
+  }
+}
diff --git a/src/net/infonode/docking/TabWindowMover.java b/src/net/infonode/docking/TabWindowMover.java
new file mode 100644
index 0000000..d0b08a8
--- /dev/null
+++ b/src/net/infonode/docking/TabWindowMover.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabWindowMover.java,v 1.17 2005/04/15 15:57:08 johan Exp $
+package net.infonode.docking;
+
+import net.infonode.tabbedpanel.TabAdapter;
+import net.infonode.tabbedpanel.TabDragEvent;
+import net.infonode.tabbedpanel.TabEvent;
+import net.infonode.tabbedpanel.TabbedPanel;
+
+/**
+ * @author $Author: johan $
+ * @version $Revision: 1.17 $
+ */
+class TabWindowMover extends TabAdapter {
+  private AbstractTabWindow window;
+  private TabbedPanel tabbedPanel;
+  private WindowDragger dragger;
+
+  TabWindowMover(AbstractTabWindow window, TabbedPanel tabbedPanel) {
+    this.window = window;
+    this.tabbedPanel = tabbedPanel;
+  }
+
+  public void tabDragged(TabDragEvent event) {
+    if (dragger == null) {
+      DockingWindow w = ((WindowTab) event.getTab()).getWindow();
+
+      if (!w.getWindowProperties().getDragEnabled())
+        return;
+
+      dragger = new WindowDragger(w);
+      window.setDraggedTabIndex(tabbedPanel.getTabIndex(event.getTab()));
+    }
+
+/*    if (tabbedPanel.tabAreaContainsPoint(SwingUtilities.convertPoint(event.getTab(), event.getPoint(), tabbedPanel)))
+      dragger.abort();
+    else*/
+    dragger.dragWindow(event.getMouseEvent());
+  }
+
+  public void tabDropped(TabDragEvent event) {
+    if (dragger != null) {
+      dragger.dropWindow(event.getMouseEvent());
+      dragger = null;
+    }
+  }
+
+  public void tabDragAborted(TabEvent event) {
+    if (dragger != null) {
+      dragger.abortDrag();
+      dragger = null;
+    }
+  }
+
+}
diff --git a/src/net/infonode/docking/View.java b/src/net/infonode/docking/View.java
new file mode 100644
index 0000000..d54bf04
--- /dev/null
+++ b/src/net/infonode/docking/View.java
@@ -0,0 +1,551 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: View.java,v 1.66 2005/12/03 14:34:34 jesper Exp $
+package net.infonode.docking;
+
+import net.infonode.docking.drag.DockingWindowDragSource;
+import net.infonode.docking.drag.DockingWindowDragger;
+import net.infonode.docking.drag.DockingWindowDraggerProvider;
+import net.infonode.docking.drop.InteriorDropInfo;
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.ViewTitleBar;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.internalutil.DropAction;
+import net.infonode.docking.model.ViewItem;
+import net.infonode.docking.model.ViewWriter;
+import net.infonode.docking.properties.ViewProperties;
+import net.infonode.docking.properties.ViewTitleBarProperties;
+import net.infonode.gui.ComponentUtil;
+import net.infonode.gui.panel.SimplePanel;
+import net.infonode.properties.base.Property;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.properties.propertymap.PropertyMapWeakListenerManager;
+import net.infonode.properties.util.PropertyChangeListener;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.util.ChangeNotifyList;
+import net.infonode.util.Direction;
+import net.infonode.util.StreamUtil;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.*;
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+/**
+ * <p>
+ * A view is a docking window containing a component.
+ * </p>
+ *
+ * <p>
+ * A view can also contain a title bar that can be shown on either side of the view component.
+ * The title bar is made visible by setting the visible property in the ViewTitleBarProperties
+ * in the ViewProperties for this view. The title bar automatically inherits the view's title
+ * and icon but it's possible to specify a specific title and icon for the title bar in the
+ * ViewTitleBarProperties in the ViewProperties for this view.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.66 $
+ * @see net.infonode.docking.properties.ViewProperties
+ * @see net.infonode.docking.properties.ViewTitleBarProperties
+ */
+public class View extends DockingWindow {
+  private Component lastFocusedComponent;
+  private HierarchyListener focusComponentListener = new HierarchyListener() {
+    public void hierarchyChanged(HierarchyEvent e) {
+      checkLastFocusedComponent();
+    }
+  };
+  private SimplePanel contentPanel = new SimplePanel();
+  private ViewProperties rootProperties = new ViewProperties();
+  private WeakReference lastRootWindow;
+  private PropertyChangeListener listener = new PropertyChangeListener() {
+    public void propertyChanged(Property property, Object valueContainer, Object oldValue, Object newValue) {
+      fireTitleChanged();
+    }
+  };
+
+  private PropertyChangeListener titleBarPropertiesListener = new PropertyChangeListener() {
+    public void propertyChanged(Property property, Object valueContainer, Object oldValue, Object newValue) {
+      updateTitleBar(property, valueContainer);
+    }
+  };
+
+  private ViewTitleBar titleBar;
+  private boolean isfocused = false;
+  private List customTitleBarComponents;
+
+  private WindowTab ghostTab;
+
+  private DropAction titleBarDropAction = new DropAction() {
+    public boolean showTitle() {
+      return false;
+    }
+
+    public void execute(DockingWindow window, MouseEvent mouseEvent) {
+      removeGhostTab();
+
+      ((AbstractTabWindow) getWindowParent()).addTab(window);
+    }
+
+    public void clear(DockingWindow window, DropAction newDropAction) {
+      if (newDropAction != this)
+        removeGhostTab();
+    }
+
+    private void removeGhostTab() {
+      if (ghostTab != null) {
+        TabbedPanel tp = ((AbstractTabWindow) getWindowParent()).getTabbedPanel();
+        tp.removeTab(ghostTab);
+        ghostTab = null;
+
+        if (tp.getProperties().getEnsureSelectedTabVisible() && tp.getSelectedTab() != null) {
+          tp.scrollTabToVisibleArea(tp.getSelectedTab());
+        }
+      }
+    }
+
+  };
+
+
+  /**
+   * Constructor.
+   *
+   * @param title     the title of the view
+   * @param icon      the icon for the view
+   * @param component the component to place inside the view
+   */
+  public View(String title, Icon icon, Component component) {
+    super(new ViewItem());
+    rootProperties.setTitle(title);
+    rootProperties.setIcon(icon);
+    getViewProperties().addSuperObject(rootProperties);
+    super.setComponent(contentPanel);
+    contentPanel.setComponent(component);
+
+    PropertyMapWeakListenerManager.addWeakPropertyChangeListener(getViewProperties().getMap(),
+                                                                 ViewProperties.TITLE,
+                                                                 listener);
+    PropertyMapWeakListenerManager.addWeakPropertyChangeListener(getViewProperties().getMap(),
+                                                                 ViewProperties.ICON,
+                                                                 listener);
+
+    PropertyMapWeakListenerManager
+        .addWeakPropertyChangeListener(getViewProperties().getViewTitleBarProperties().getMap(),
+                                       ViewTitleBarProperties.VISIBLE,
+                                       titleBarPropertiesListener);
+    PropertyMapWeakListenerManager
+        .addWeakPropertyChangeListener(getViewProperties().getViewTitleBarProperties().getMap(),
+                                       ViewTitleBarProperties.CONTENT_TITLE_BAR_GAP,
+                                       titleBarPropertiesListener);
+    PropertyMapWeakListenerManager
+        .addWeakPropertyChangeListener(getViewProperties().getViewTitleBarProperties().getMap(),
+                                       ViewTitleBarProperties.ORIENTATION,
+                                       titleBarPropertiesListener);
+
+    updateTitleBar(null, null);
+
+    init();
+  }
+
+  /**
+   * <p>
+   * Returns a list containing the custom window tab components. Changes to the list will be propagated to the tab.
+   * </p>
+   * <p>
+   * The custom tab components will be shown after the window title when the window tab is highlighted. The
+   * components are shown in the same order as they appear in the list. The custom tab components container layout is
+   * rotated with the tab direction.
+   * </p>
+   *
+   * @return a list containing the custom tab components, list elements are of type {@link JComponent}
+   * @since IDW 1.3.0
+   */
+  public java.util.List getCustomTabComponents() {
+    return getTab().getCustomTabComponentsList();
+  }
+
+  /**
+   * <p>
+   * Returns a list containing the custom view title bar components. Changes to the list will be propagated to the title bar.
+   * </p>
+   * <p>
+   * The custom title bar components will be shown after the view title in the title bar but before the close, minimize and restore
+   * buttons. The components are shown in the same order as they appear in the list. The custom title bar components container
+   * layout is rotated with the title bar direction.
+   * </p>
+   * <p>
+   * <strong>Note:</strong> The components are only shon if the title bar is visible, see {@link ViewTitleBarProperties}.
+   * </p>
+   *
+   * @return a list containing the custom title bar components, list elements are of type {@link JComponent}
+   * @since IDW 1.4.0
+   */
+  public List getCustomTitleBarComponents() {
+    if (customTitleBarComponents == null)
+      customTitleBarComponents = new ChangeNotifyList() {
+        protected void changed() {
+          if (titleBar != null)
+            titleBar.updateCustomBarComponents(this);
+        }
+      };
+
+    return customTitleBarComponents;
+  }
+
+  /**
+   * Gets the component inside the view.
+   *
+   * @return the component inside the view
+   * @since IDW 1.1.0
+   */
+  public Component getComponent() {
+    return contentPanel.getComponent(0);
+  }
+
+  /**
+   * Sets the component inside the view.
+   *
+   * @param component the component to place inside the view
+   * @since IDW 1.1.0
+   */
+  public void setComponent(Component component) {
+    contentPanel.setComponent(component);
+  }
+
+  /**
+   * Returns the property values for this view.
+   *
+   * @return the property values for this view
+   */
+  public ViewProperties getViewProperties() {
+    return ((ViewItem) getWindowItem()).getViewProperties();
+  }
+
+  protected void update() {
+    // TODO:
+  }
+
+  public DockingWindow getChildWindow(int index) {
+    return null;
+  }
+
+  public int getChildWindowCount() {
+    return 0;
+  }
+
+  void setLastFocusedComponent(Component component) {
+    if (component != lastFocusedComponent) {
+      if (lastFocusedComponent != null)
+        lastFocusedComponent.removeHierarchyListener(focusComponentListener);
+
+//      System.out.println("Focus: " + this + ", " + component.getClass() + " " + System.identityHashCode(component));
+      lastFocusedComponent = component;
+
+      if (lastFocusedComponent != null)
+        lastFocusedComponent.addHierarchyListener(focusComponentListener);
+    }
+  }
+
+  Component getFocusComponent() {
+    checkLastFocusedComponent();
+    return lastFocusedComponent;
+  }
+
+  public boolean isFocusCycleRoot() {
+    return true;
+  }
+
+  /**
+   * Restores focus to the last focused child component or, if no child component has had focus,
+   * the first focusable component inside the view.
+   *
+   * @since IDW 1.1.0
+   */
+  public void restoreFocus() {
+    makeVisible();
+    checkLastFocusedComponent();
+
+    if (lastFocusedComponent == null) {
+      ComponentUtil.smartRequestFocus(contentPanel);
+    }
+    else {
+//      System.out.println("Restore: " + this + ", " + lastFocusedComponent.getClass() + " " + System.identityHashCode(lastFocusedComponent));
+      lastFocusedComponent.requestFocusInWindow();
+    }
+  }
+
+  public Icon getIcon() {
+    return getViewProperties().getIcon();
+  }
+
+  protected void doReplace(DockingWindow oldWindow, DockingWindow newWindow) {
+    throw new RuntimeException(View.class + ".replaceChildWindow called!");
+  }
+
+  protected void doRemoveWindow(DockingWindow window) {
+    throw new RuntimeException(View.class + ".removeChildWindow called!");
+  }
+
+  protected void write(ObjectOutputStream out, WriteContext context) throws IOException {
+    ByteArrayOutputStream baos = new ByteArrayOutputStream();
+    ObjectOutputStream oos = new ObjectOutputStream(baos);
+
+    try {
+      context.getViewSerializer().writeView(this, oos);
+      getWindowItem().writeSettings(oos, context);
+    }
+    finally {
+      oos.close();
+    }
+
+    out.writeInt(baos.size());
+    baos.writeTo(out);
+  }
+
+  static View read(ObjectInputStream in, ReadContext context) throws IOException {
+    int size = in.readInt();
+    byte[] viewData = new byte[size];
+    StreamUtil.readAll(in, viewData);
+    ObjectInputStream viewIn = new ObjectInputStream(new ByteArrayInputStream(viewData));
+    View view = context.getViewSerializer().readView(viewIn);
+
+    if (view != null)
+      view.getWindowItem().readSettings(viewIn, context);
+
+    return view;
+  }
+
+  protected DropAction doAcceptDrop(Point p, DockingWindow window) {
+    if (getWindowParent() instanceof TabWindow && titleBar != null &&
+        titleBar.contains(SwingUtilities.convertPoint(this, p, titleBar))) {
+      return acceptInteriorDrop(p, window);
+    }
+
+    return getWindowParent() instanceof TabWindow && getWindowParent().getChildWindowCount() == 1 ?
+           null :
+           super.doAcceptDrop(p, window);
+  }
+
+  protected DropAction acceptInteriorDrop(Point p, DockingWindow window) {
+    if (getWindowParent() instanceof TabWindow && titleBar != null && window.getWindowParent() != getWindowParent()) {
+      Point p2 = SwingUtilities.convertPoint(this, p, titleBar);
+      if (titleBar.contains(p2)) {
+        if (!getInteriorDropFilter().acceptDrop(new InteriorDropInfo(window, this, p)))
+          return null;
+
+        addGhostTab(window);
+        Component c = getWindowParent() instanceof TabWindow ? getWindowParent() : this;
+        getRootWindow().setDragRectangle(SwingUtilities.convertRectangle(c,
+                                                                         new Rectangle(0,
+                                                                                       0,
+                                                                                       c.getWidth(),
+                                                                                       c.getHeight()),
+                                                                         getRootWindow()));
+
+        return titleBarDropAction;
+      }
+    }
+
+    return null;
+  }
+
+  private void addGhostTab(DockingWindow window) {
+    if (ghostTab == null) {
+      ghostTab = ((AbstractTabWindow) getWindowParent()).createGhostTab(window);
+      ((AbstractTabWindow) getWindowParent()).getTabbedPanel().addTab(ghostTab);
+      ((AbstractTabWindow) getWindowParent()).getTabbedPanel().scrollTabToVisibleArea(ghostTab);
+    }
+  }
+
+  public String toString() {
+    return getTitle();
+  }
+
+  void setRootWindow(RootWindow newRoot) {
+    if (newRoot == null)
+      return;
+
+    RootWindow last = lastRootWindow == null ? null : (RootWindow) lastRootWindow.get();
+
+    if (last == newRoot)
+      return;
+
+    if (last != null)
+      last.removeView(this);
+
+    lastRootWindow = new WeakReference(newRoot);
+    newRoot.addView(this);
+  }
+
+  protected void setFocused(boolean focused) {
+    super.setFocused(focused);
+    if (isfocused != focused) {
+      isfocused = focused;
+
+      if (focused)
+        getViewProperties().getViewTitleBarProperties().getNormalProperties()
+            .addSuperObject(getViewProperties().getViewTitleBarProperties().getFocusedProperties());
+      else
+        getViewProperties().getViewTitleBarProperties().getNormalProperties()
+            .removeSuperObject(getViewProperties().getViewTitleBarProperties().getFocusedProperties());
+    }
+  }
+
+  protected void rootChanged(final RootWindow oldRoot, final RootWindow newRoot) {
+    super.rootChanged(oldRoot, newRoot);
+    setRootWindow(newRoot);
+
+    // TODO: eliminate root window = null because triggers property updates.
+    if (oldRoot != getRootWindow()) {
+      if (oldRoot != null)
+        rootProperties.removeSuperObject(oldRoot.getRootWindowProperties().getViewProperties());
+
+      if (getRootWindow() != null) {
+        rootProperties.addSuperObject(getRootWindow().getRootWindowProperties().getViewProperties());
+      }
+    }
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return getViewProperties().getMap();
+  }
+
+  protected PropertyMap createPropertyObject() {
+    return new ViewProperties().getMap();
+  }
+
+  protected boolean needsTitleWindow() {
+    return getViewProperties().getAlwaysShowTitle();
+  }
+
+  private void checkLastFocusedComponent() {
+    if (lastFocusedComponent != null && !SwingUtilities.isDescendingFrom(lastFocusedComponent, this)) {
+      lastFocusedComponent.removeHierarchyListener(focusComponentListener);
+      lastFocusedComponent = null;
+    }
+  }
+
+  void removeWindowComponent(DockingWindow window) {
+  }
+
+  void restoreWindowComponent(DockingWindow window) {
+  }
+
+  private void updateTitleBar(Property property, Object valueContainer) {
+    boolean changed = valueContainer == null;
+
+    ViewTitleBarProperties titleBarProperties = getViewProperties().getViewTitleBarProperties();
+    //System.out.println("Updating title bar " + changed + "  " + property);
+
+    if (changed || property == ViewTitleBarProperties.VISIBLE) {
+      if (titleBarProperties.getVisible()) {
+        if (titleBar == null) {
+          titleBar = new ViewTitleBar(this);
+          new DockingWindowDragSource(titleBar, new DockingWindowDraggerProvider() {
+            public DockingWindowDragger getDragger(MouseEvent mouseEvent) {
+              return getWindowProperties().getDragEnabled() ? startDrag(getRootWindow()) : null;
+            }
+          });
+          titleBar.addMouseListener(new MouseAdapter() {
+            public void mousePressed(MouseEvent e) {
+              fireTabWindowMouseButtonEvent(e);
+              checkPopupMenu(e);
+            }
+
+            public void mouseClicked(MouseEvent e) {
+              fireTabWindowMouseButtonEvent(e);
+            }
+
+            public void mouseReleased(MouseEvent e) {
+              fireTabWindowMouseButtonEvent(e);
+              checkPopupMenu(e);
+            }
+
+            private void checkPopupMenu(MouseEvent e) {
+              if (e.isPopupTrigger()) {
+                showPopupMenu(e);
+              }
+            }
+          });
+
+          if (customTitleBarComponents != null)
+            titleBar.updateCustomBarComponents(customTitleBarComponents);
+          changed = true;
+        }
+      }
+      else {
+        if (titleBar != null) {
+          remove(titleBar);
+          titleBar.dispose();
+          titleBar = null;
+
+          changed = true;
+        }
+      }
+    }
+
+    if (changed || property == ViewTitleBarProperties.ORIENTATION) {
+      if (titleBar != null) {
+        remove(titleBar);
+        add(titleBar, ComponentUtil.getBorderLayoutOrientation(titleBarProperties.getOrientation()));
+
+        changed = true;
+      }
+    }
+
+    if (changed || property == ViewTitleBarProperties.CONTENT_TITLE_BAR_GAP) {
+      if (titleBar != null) {
+        Direction orientation = titleBarProperties.getOrientation();
+        int contentBarGap = titleBarProperties.getContentTitleBarGap();
+        contentPanel.setBorder(new EmptyBorder(orientation == Direction.UP ? contentBarGap : 0,
+                                               orientation == Direction.LEFT ? contentBarGap : 0,
+                                               orientation == Direction.DOWN
+                                               ? contentBarGap
+                                               : 0,
+                                               orientation == Direction.RIGHT ? contentBarGap : 0));
+      }
+      else {
+        contentPanel.setBorder(null);
+      }
+    }
+  }
+
+  protected void updateButtonVisibility() {
+    super.updateButtonVisibility();
+
+    if (titleBar != null)
+      titleBar.updateViewButtons(null);
+  }
+
+  protected void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+    out.writeInt(WindowIds.VIEW);
+    viewWriter.writeView(this, out, context);
+  }
+}
diff --git a/src/net/infonode/docking/ViewSerializer.java b/src/net/infonode/docking/ViewSerializer.java
new file mode 100644
index 0000000..6c55bcc
--- /dev/null
+++ b/src/net/infonode/docking/ViewSerializer.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ViewSerializer.java,v 1.5 2004/09/22 14:31:39 jesper Exp $
+package net.infonode.docking;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * Reads and writes the state of a view.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public interface ViewSerializer {
+  /**
+   * Writes a view to a stream.
+   * Note that the view property values are written automatically, so this method should not write them.
+   *
+   * @param view the view to write
+   * @param out  the stream on which to write the view
+   * @throws IOException if there is a stream error
+   */
+  void writeView(View view, ObjectOutputStream out) throws IOException;
+
+  /**
+   * Reads and returns a view.
+   * Must read all the data written in the {@link #writeView} method.
+   * Note that the view property values are read automatically, so this method should not read them.
+   * This method should return null if the serialized view can't be resolved.
+   *
+   * @param in the stream from which to read the view state
+   * @return the view, null if the view can't be resolved
+   * @throws IOException if there is a stream error
+   */
+  View readView(ObjectInputStream in) throws IOException;
+
+}
diff --git a/src/net/infonode/docking/WindowBar.java b/src/net/infonode/docking/WindowBar.java
new file mode 100644
index 0000000..17043b7
--- /dev/null
+++ b/src/net/infonode/docking/WindowBar.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowBar.java,v 1.69 2007/05/18 11:23:32 johan Exp $
+package net.infonode.docking;
+
+import net.infonode.docking.internal.HeavyWeightContainer;
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.internalutil.DropAction;
+import net.infonode.docking.model.ViewReader;
+import net.infonode.docking.model.ViewWriter;
+import net.infonode.docking.model.WindowBarItem;
+import net.infonode.docking.properties.TabWindowProperties;
+import net.infonode.docking.properties.WindowBarProperties;
+import net.infonode.docking.util.DockingUtil;
+import net.infonode.gui.panel.BaseContainerUtil;
+import net.infonode.gui.panel.ResizablePanel;
+import net.infonode.properties.base.Property;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.properties.propertymap.PropertyMapWeakListenerManager;
+import net.infonode.properties.util.PropertyChangeListener;
+import net.infonode.tabbedpanel.TabContentPanel;
+import net.infonode.tabbedpanel.TabbedPanelContentPanel;
+import net.infonode.util.Direction;
+
+import java.awt.*;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * A window bar is located at the edge of a root window.
+ * It's a tabbed panel where the content panel is dynamically shown and hidden.
+ * A window bar is enabled and disabled using the {@link Component#setEnabled} method.
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.69 $
+ */
+public class WindowBar extends AbstractTabWindow {
+  private RootWindow rootWindow;
+  private Direction direction;
+  private TabbedPanelContentPanel contentPanel;
+  private ResizablePanel edgePanel;
+  private HeavyWeightContainer heavyWeightEdgePanel;
+
+  private PropertyChangeListener opaqueListener = new PropertyChangeListener() {
+    public void propertyChanged(Property property, Object valueContainer, Object oldValue, Object newValue) {
+      updateEdgePanelOpaque();
+    }
+  };
+
+  WindowBar(RootWindow rootWindow, Direction direction) {
+    super(false, new WindowBarItem());
+
+    initMouseListener();
+
+    this.rootWindow = rootWindow;
+    contentPanel = new TabbedPanelContentPanel(getTabbedPanel(), new TabContentPanel(getTabbedPanel()) {
+      public Dimension getMinimumSize() {
+        if (getWindowBarProperties().getTabWindowProperties().getRespectChildWindowMinimumSize())
+          return super.getMinimumSize();
+
+        return new Dimension(0, 0);
+      }
+    });
+
+    this.direction = direction;
+
+    {
+      WindowBarProperties properties = new WindowBarProperties();
+      properties.getTabWindowProperties().addSuperObject(rootWindow.getRootWindowProperties().getTabWindowProperties());
+
+      ((WindowBarItem) getWindowItem()).setWindowBarProperties(new WindowBarProperties(properties));
+      getWindowBarProperties().addSuperObject(rootWindow.getRootWindowProperties().getWindowBarProperties());
+      getWindowBarProperties().addSuperObject(WindowBarProperties.createDefault(this.direction));
+    }
+
+    edgePanel = new ResizablePanel(rootWindow.isHeavyweightSupported(), this.direction.getOpposite(), contentPanel);
+    edgePanel.setPreferredSize(new Dimension(200, 200));
+    //edgePanel.setVisible(false);
+    edgePanel.setComponent(contentPanel);
+    edgePanel.setLayeredPane(rootWindow.getLayeredPane());
+    edgePanel.setInnerArea(rootWindow.getWindowPanel());
+
+    updateEdgePanelOpaque();
+
+    PropertyMapWeakListenerManager.addWeakPropertyChangeListener(
+        contentPanel.getProperties().getShapedPanelProperties().getMap(),
+        ShapedPanelProperties.OPAQUE,
+        opaqueListener);
+
+    if (rootWindow.isHeavyweightSupported()) {
+      edgePanel.addComponentListener(new ComponentAdapter() {
+        public void componentResized(ComponentEvent e) {
+          if (edgePanel.getParent() != null)
+            edgePanel.getParent().repaint();
+        }
+      });
+    }
+
+    getEdgePanel().setVisible(false);
+
+    setTabWindowProperties(getWindowBarProperties().getTabWindowProperties());
+
+    init();
+  }
+
+  public TabWindowProperties getTabWindowProperties() {
+    return getWindowBarProperties().getTabWindowProperties();
+  }
+
+  /**
+   * Returns the property values for this window bar.
+   *
+   * @return the property values for this window bar
+   */
+  public WindowBarProperties getWindowBarProperties() {
+    return ((WindowBarItem) getWindowItem()).getWindowBarProperties();
+  }
+
+  protected int addTabNoSelect(DockingWindow window, int index) {
+    index = super.addTabNoSelect(window, index);
+    window.setLastMinimizedDirection(direction);
+    return index;
+  }
+
+  /**
+   * Sets the size of the content panel.
+   * If the window bar is located on the left or right side, the panel width is set otherwise the panel height.
+   *
+   * @param size the content panel size
+   */
+  public void setContentPanelSize(int size) {
+    edgePanel.setPreferredSize(direction.isHorizontal() ? new Dimension(size, 0) : new Dimension(0, size));
+  }
+
+  /**
+   * Returns the size of the content panel.
+   * If the window bar is located on the left or right side, the panel width is returned otherwise the panel height.
+   *
+   * @return the size of the content panel
+   */
+  public int getContentPanelSize() {
+    Dimension size = edgePanel.getPreferredSize();
+    return direction.isHorizontal() ? size.width : size.height;
+  }
+
+  /**
+   * Returns the window bar direction in the root window it is a member of
+   *
+   * @return window bar direction in root window
+   * @since IDW 1.4.0
+   */
+  public Direction getDirection() {
+    return direction;
+  }
+
+  public RootWindow getRootWindow() {
+    return rootWindow;
+  }
+
+  protected void showChildWindow(DockingWindow window) {
+    int index = getChildWindowIndex(window);
+
+    if (index != -1)
+      setSelectedTab(index);
+
+    super.showChildWindow(window);
+  }
+
+  Component getEdgePanel() {
+    if (!rootWindow.isHeavyweightSupported())
+      return edgePanel;
+
+    if (heavyWeightEdgePanel == null) {
+      //edgePanel.setOpaque(true);
+      heavyWeightEdgePanel = new HeavyWeightContainer(edgePanel) {
+        // 2007-05-18: Overrided as workaround for repaint problem while using heavyweight in Java 1.6.
+        public void setVisible(boolean v) {
+          if (getRootWindow() != null)
+            getRootWindow().paintImmediately(0, 0, getRootWindow().getWidth(), getRootWindow().getHeight());
+          super.setVisible(v);
+        }
+      };
+      heavyWeightEdgePanel.setVisible(false);
+    }
+
+    return heavyWeightEdgePanel;
+  }
+
+  protected void update() {
+    edgePanel.setResizeWidth(getWindowBarProperties().getContentPanelEdgeResizeDistance());
+    edgePanel.setContinuousLayout(getWindowBarProperties().getContinuousLayoutEnabled());
+    edgePanel.setDragIndicatorColor(getWindowBarProperties().getDragIndicatorColor());
+    getWindowBarProperties().getComponentProperties().applyTo(this, direction.getNextCW());
+  }
+
+  private void updateEdgePanelOpaque() {
+    if (edgePanel != null)
+      BaseContainerUtil.setForcedOpaque(edgePanel,
+                                        rootWindow.isHeavyweightSupported() || contentPanel.getProperties()
+                                            .getShapedPanelProperties()
+                                            .getOpaque());
+    //edgePanel.setForcedOpaque(rootWindow.isHeavyweightSupported() ? true : contentPanel.getProperties().getShapedPanelProperties().getOpaque());
+  }
+
+  public Dimension getPreferredSize() {
+    if (isEnabled()) {
+      Dimension size = super.getPreferredSize();
+      int minWidth = getWindowBarProperties().getMinimumWidth();
+      return new Dimension(Math.max(minWidth, size.width), Math.max(minWidth, size.height));
+    }
+    else
+      return new Dimension(0, 0);
+  }
+
+  protected void tabSelected(WindowTab tab) {
+    getEdgePanel().setVisible(tab != null);
+    //edgePanel.setVisible(tab != null);
+    super.tabSelected(tab);
+  }
+
+  protected boolean isInsideTabArea(Point p2) {
+    return true;
+  }
+
+  protected void clearFocus(View view) {
+    super.clearFocus(view);
+
+    if (view != null && !DockingUtil.isAncestor(this, view)) {
+      getTabbedPanel().setSelectedTab(null);
+    }
+  }
+
+  public boolean isMinimized() {
+    return true;
+  }
+
+  protected boolean acceptsSplitWith(DockingWindow window) {
+    return false;
+  }
+
+  DropAction acceptDrop(Point p, DockingWindow window) {
+    return isEnabled() ? super.acceptDrop(p, window) : null;
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return getWindowBarProperties().getMap();
+  }
+
+  protected PropertyMap createPropertyObject() {
+    return new WindowBarProperties().getMap();
+  }
+
+  protected void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+    out.writeInt(getContentPanelSize());
+    out.writeBoolean(isEnabled());
+    getWindowItem().writeSettings(out, context);
+    super.write(out, context, viewWriter);
+  }
+
+  protected DockingWindow newRead(ObjectInputStream in, ReadContext context, ViewReader viewReader) throws IOException {
+    setContentPanelSize(in.readInt());
+    setEnabled(in.readBoolean());
+    getWindowItem().readSettings(in, context);
+    super.newRead(in, context, viewReader);
+    return this;
+  }
+
+  protected DockingWindow oldRead(ObjectInputStream in, ReadContext context) throws IOException {
+    super.oldRead(in, context);
+    setContentPanelSize(in.readInt());
+    setEnabled(in.readBoolean());
+    return this;
+  }
+
+}
diff --git a/src/net/infonode/docking/WindowDecoder.java b/src/net/infonode/docking/WindowDecoder.java
new file mode 100644
index 0000000..c716d02
--- /dev/null
+++ b/src/net/infonode/docking/WindowDecoder.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowDecoder.java,v 1.19 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking;
+
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.model.SplitWindowItem;
+import net.infonode.docking.model.TabWindowItem;
+import net.infonode.docking.model.ViewReader;
+import net.infonode.docking.model.WindowItem;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.19 $
+ */
+class WindowDecoder {
+  private WindowDecoder() {
+  }
+
+  static DockingWindow decodeWindow(ObjectInputStream in, ReadContext context) throws IOException {
+    int id = in.readInt();
+
+    switch (id) {
+      case WindowIds.VIEW:
+        return View.read(in, context);
+
+      case WindowIds.SPLIT: {
+        SplitWindow w = new SplitWindow(true);
+        return w.oldRead(in, context);
+      }
+
+      case WindowIds.TAB: {
+        TabWindow w = new TabWindow();
+        return w.oldRead(in, context);
+      }
+
+      default:
+        throw new IOException("Invalid window ID: " + id + '!');
+    }
+  }
+
+  static DockingWindow decodeWindow(ObjectInputStream in, ReadContext context, ViewReader viewReader) throws
+                                                                                                      IOException {
+    int id = in.readInt();
+
+    if (id == WindowIds.VIEW) {
+      return viewReader.readView(in, context);
+    }
+    else {
+      WindowItem windowItem = viewReader.readWindowItem(in, context);
+
+      switch (id) {
+        case WindowIds.SPLIT: {
+          SplitWindowItem item = (SplitWindowItem) windowItem;
+
+          if (item == null) {
+            item = new SplitWindowItem();
+            item.readSettings(in, context);
+          }
+
+          SplitWindow w = new SplitWindow(item.isHorizontal(),
+                                          item.getDividerLocation(),
+                                          null,
+                                          null,
+                                          item);
+          return w.newRead(in, context, viewReader);
+        }
+
+        case WindowIds.TAB: {
+          TabWindowItem item = (TabWindowItem) windowItem;
+
+          if (item == null) {
+            item = new TabWindowItem();
+            item.readSettings(in, context);
+          }
+
+          TabWindow w = new TabWindow(null, item);
+          return w.newRead(in, context, viewReader);
+        }
+
+        default:
+          throw new IOException("Invalid window ID: " + id + '!');
+      }
+    }
+  }
+}
diff --git a/src/net/infonode/docking/WindowDragger.java b/src/net/infonode/docking/WindowDragger.java
new file mode 100644
index 0000000..67ea055
--- /dev/null
+++ b/src/net/infonode/docking/WindowDragger.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowDragger.java,v 1.31 2008/04/04 12:42:15 jesper Exp $
+package net.infonode.docking;
+
+import net.infonode.docking.drag.DockingWindowDragger;
+import net.infonode.docking.internalutil.DropAction;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.dnd.DragSource;
+import java.awt.event.MouseEvent;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.31 $
+ */
+class WindowDragger implements DockingWindowDragger {
+  private DockingWindow dragWindow;
+  private DropAction dropAction;
+  private RootWindow rootWindow;
+
+  WindowDragger(DockingWindow dragWindow) {
+    this(dragWindow, dragWindow.getRootWindow());
+  }
+
+  WindowDragger(DockingWindow dragWindow, RootWindow rootWindow) {
+    this.dragWindow = dragWindow;
+    this.rootWindow = rootWindow;
+
+    rootWindow.internalStartDrag(dragWindow);
+  }
+
+  public DockingWindow getDragWindow() {
+    return dragWindow;
+  }
+
+  public RootWindow getDropTarget() {
+    return rootWindow;
+  }
+
+  void undoDrag(DropAction newAction) {
+    if (dropAction != null) {
+      dropAction.clear(dragWindow, newAction);
+      dropAction = null;
+    }
+  }
+
+  private void stopDrag() {
+    rootWindow.stopDrag();
+  }
+
+  public void abortDrag() {
+    stopDrag();
+    undoDrag(null);
+  }
+
+  public void dropWindow(MouseEvent mouseEvent) {
+    stopDrag();
+
+    if (dropAction == null)
+      dropAction = dragWindow.getDefaultDropAction();
+
+    if (dragWindow != null && dropAction != null) {
+      dropAction.execute(dragWindow, mouseEvent);
+
+      Component c = dragWindow.getTopLevelAncestor();
+      if (c != null && c instanceof Window)
+        ((Window) c).toFront();
+
+      FocusManager.focusWindow(dragWindow);
+    }
+  }
+
+  public void dragWindow(MouseEvent mouseEvent) {
+    JRootPane root = rootWindow.getCurrentDragRootPane();
+    
+    Point point = SwingUtilities.convertPoint((Component) mouseEvent.getSource(),
+                                              mouseEvent.getPoint(),
+                                              root);
+
+    if (root != rootWindow.getRootPane() && !root.contains(point)) {
+      rootWindow.setCurrentDragRootPane(null);
+      root = rootWindow.getCurrentDragRootPane();
+      point = SwingUtilities.convertPoint((Component) mouseEvent.getSource(),
+                                          mouseEvent.getPoint(),
+                                          root);
+    }
+
+    DockingWindow dropWindow = getDeepestWindowAt(root, point.x, point.y);
+
+    while (dropWindow != null && dropWindow.getWindowParent() != null &&
+           !(dropWindow/*.getWindowParent()*/ instanceof FloatingWindow)) {
+      Point p2 = SwingUtilities.convertPoint(root, point, dropWindow.getWindowParent());
+
+      if (!dropWindow.getWindowParent().contains(p2))
+        break;
+
+      dropWindow = dropWindow.getWindowParent();
+    }
+
+    DropAction da = dropWindow != null ?
+                    dropWindow.acceptDrop(SwingUtilities.convertPoint(root, point, dropWindow), dragWindow) : null;
+
+    undoDrag(da);
+
+    Cursor cursor = DragSource.DefaultMoveDrop;
+    if (da == null)
+      cursor = DragSource.DefaultMoveNoDrop;
+
+    if (dropWindow == null && dragWindow.getWindowProperties().getUndockOnDropEnabled())
+      cursor = DragSource.DefaultMoveDrop;
+
+    rootWindow.setDragCursor(cursor);
+    rootWindow.setDragText(da == null || da.showTitle() ? point : null, dragWindow.getTitle());
+    dropAction = da;
+
+    if (dropAction == null)
+      rootWindow.setDragRectangle(null);
+  }
+
+  private DockingWindow getDeepestWindowAt(Component component, int x, int y) {
+    if (component == null || !component.isVisible() || !component.contains(x, y))
+      return null;
+
+    if (component instanceof Container) {
+      Component[] components = ((Container) component).getComponents();
+
+      for (int i = 0; i < components.length; i++) {
+        DockingWindow w = getDeepestWindowAt(components[i], x - components[i].getX(), y - components[i].getY());
+
+        if (w != null)
+          return w;
+      }
+    }
+
+    if (component instanceof DockingWindow) {
+      DockingWindow w = (DockingWindow) component;
+      return w.getRootWindow() == rootWindow ? w : null;
+    }
+    else
+      return null;
+  }
+}
diff --git a/src/net/infonode/docking/WindowIds.java b/src/net/infonode/docking/WindowIds.java
new file mode 100644
index 0000000..fab5850
--- /dev/null
+++ b/src/net/infonode/docking/WindowIds.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowIds.java,v 1.6 2005/06/19 20:57:19 jesper Exp $
+package net.infonode.docking;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+class WindowIds {
+  static final int VIEW = 1;
+  static final int SPLIT = 2;
+  static final int TAB = 3;
+
+  private WindowIds() {
+  }
+}
diff --git a/src/net/infonode/docking/WindowPopupMenuFactory.java b/src/net/infonode/docking/WindowPopupMenuFactory.java
new file mode 100644
index 0000000..40f1f14
--- /dev/null
+++ b/src/net/infonode/docking/WindowPopupMenuFactory.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowPopupMenuFactory.java,v 1.4 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking;
+
+import javax.swing.*;
+
+/**
+ * Creates a popup menu for a docking window.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public interface WindowPopupMenuFactory {
+  /**
+   * Creates and returns a popup menu for a docking window.
+   *
+   * @param window the window for which to create the popup menu
+   * @return the popup menu, null for no popup menu
+   */
+  JPopupMenu createPopupMenu(DockingWindow window);
+}
diff --git a/src/net/infonode/docking/WindowTab.java b/src/net/infonode/docking/WindowTab.java
new file mode 100644
index 0000000..c173ca5
--- /dev/null
+++ b/src/net/infonode/docking/WindowTab.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowTab.java,v 1.58 2009/02/05 15:57:55 jesper Exp $
+package net.infonode.docking;
+
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.Map;
+
+import javax.swing.AbstractButton;
+import javax.swing.SwingUtilities;
+
+import net.infonode.docking.internalutil.*;
+import net.infonode.docking.properties.WindowTabProperties;
+import net.infonode.docking.properties.WindowTabStateProperties;
+import net.infonode.gui.ContainerList;
+import net.infonode.gui.panel.DirectionPanel;
+import net.infonode.gui.panel.SimplePanel;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.properties.propertymap.PropertyMapListener;
+import net.infonode.properties.propertymap.PropertyMapTreeListener;
+import net.infonode.properties.propertymap.PropertyMapWeakListenerManager;
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+import net.infonode.tabbedpanel.titledtab.TitledTabStateProperties;
+import net.infonode.util.Direction;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.58 $
+ */
+class WindowTab extends TitledTab {
+  private static final TitledTabStateProperties EMPTY_PROPERTIES = new TitledTabStateProperties();
+  private static final WindowTabProperties EMPTY_TAB_PROPERTIES = new WindowTabProperties();
+
+  private static final ButtonInfo[] buttonInfos = {
+                                                   new UndockButtonInfo(WindowTabStateProperties.UNDOCK_BUTTON_PROPERTIES),
+                                                   new DockButtonInfo(WindowTabStateProperties.DOCK_BUTTON_PROPERTIES),
+                                                   new MinimizeButtonInfo(WindowTabStateProperties.MINIMIZE_BUTTON_PROPERTIES),
+                                                   new RestoreButtonInfo(WindowTabStateProperties.RESTORE_BUTTON_PROPERTIES),
+                                                   new CloseButtonInfo(WindowTabStateProperties.CLOSE_BUTTON_PROPERTIES)};
+
+  private final DockingWindow window;
+  private final AbstractButton[][] buttons = new AbstractButton[WindowTabState.getStateCount()][];
+  private final DirectionPanel[] buttonBoxes = new DirectionPanel[WindowTabState.getStateCount()];
+  private final DirectionPanel customComponents = new DirectionPanel();
+  private final DirectionPanel highlightedFocusedPanel = new DirectionPanel() {
+    public Dimension getMinimumSize() {
+      return new Dimension(0, 0);
+    }
+  };
+  private final WindowTabProperties windowTabProperties = new WindowTabProperties(EMPTY_TAB_PROPERTIES);
+  private ContainerList tabComponentsList;
+  private boolean isFocused;
+
+  private final PropertyMapListener windowPropertiesListener = new PropertyMapListener() {
+    public void propertyValuesChanged(PropertyMap propertyObject, Map changes) {
+      updateTabButtons(null);
+    }
+  };
+
+  private final PropertyMapTreeListener windowTabPropertiesListener = new PropertyMapTreeListener() {
+    public void propertyValuesChanged(Map changes) {
+      updateTabButtons(changes);
+    }
+  };
+
+  WindowTab(DockingWindow window, boolean emptyContent) {
+    super(window.getTitle(), window.getIcon(), emptyContent ? null : new SimplePanel(window), null);
+    this.window = window;
+
+    for (int i = 0; i < WindowTabState.getStateCount(); i++) {
+      buttonBoxes[i] = new DirectionPanel() {
+        public Dimension getMinimumSize() {
+          return new Dimension(0, 0);
+        }
+      };
+      buttons[i] = new AbstractButton[buttonInfos.length];
+    }
+
+    highlightedFocusedPanel.add(customComponents);
+    highlightedFocusedPanel.add(buttonBoxes[WindowTabState.HIGHLIGHTED.getValue()]);
+    setHighlightedStateTitleComponent(highlightedFocusedPanel);
+    setNormalStateTitleComponent(buttonBoxes[WindowTabState.NORMAL.getValue()]);
+
+    addMouseListener(new MouseAdapter() {
+      public void mousePressed(MouseEvent e) {
+        getWindow().fireTabWindowMouseButtonEvent(e);
+        checkPopupMenu(e);
+      }
+
+      public void mouseClicked(MouseEvent e) {
+        getWindow().fireTabWindowMouseButtonEvent(e);
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        getWindow().fireTabWindowMouseButtonEvent(e);
+        checkPopupMenu(e);
+      }
+
+      private void checkPopupMenu(MouseEvent e) {
+        if (e.isPopupTrigger() && contains(e.getPoint())) {
+          WindowTab.this.window.showPopupMenu(e);
+        }
+      }
+
+    });
+
+    getProperties().addSuperObject(windowTabProperties.getTitledTabProperties());
+
+    PropertyMapWeakListenerManager.addWeakTreeListener(windowTabProperties.getMap(), windowTabPropertiesListener);
+
+    PropertyMapWeakListenerManager.addWeakListener(this.window.getWindowProperties().getMap(),
+        windowPropertiesListener);
+
+    windowTabProperties.getTitledTabProperties().getHighlightedProperties().addSuperObject(EMPTY_PROPERTIES);
+  }
+
+  public void updateUI() {
+    super.updateUI();
+
+    if (buttonBoxes != null)
+      for (int i = 0; i < WindowTabState.getStateCount(); i++)
+        if (buttonBoxes[i] != null)
+          SwingUtilities.updateComponentTreeUI(buttonBoxes[i]);
+  }
+
+  void setFocused(boolean focused) {
+    if (isFocused != focused) {
+      isFocused = focused;
+      TitledTabStateProperties properties = focused ? windowTabProperties.getFocusedProperties() : EMPTY_PROPERTIES;
+      windowTabProperties.getTitledTabProperties().getHighlightedProperties().getMap().
+      replaceSuperMap(
+          windowTabProperties.getTitledTabProperties().getHighlightedProperties().getMap().getSuperMap(),
+          properties.getMap());
+      highlightedFocusedPanel.remove(1);
+      highlightedFocusedPanel.add(
+          buttonBoxes[focused ? WindowTabState.FOCUSED.getValue() : WindowTabState.HIGHLIGHTED.getValue()]);
+      highlightedFocusedPanel.revalidate();
+    }
+  }
+
+  void setProperties(WindowTabProperties properties) {
+    windowTabProperties.getMap().replaceSuperMap(windowTabProperties.getMap().getSuperMap(), properties.getMap());
+  }
+
+  void unsetProperties() {
+    setProperties(EMPTY_TAB_PROPERTIES);
+  }
+
+  void updateTabButtons(Map changes) {
+    WindowTabState[] states = WindowTabState.getStates();
+
+    for (int i = 0; i < states.length; i++) {
+      WindowTabState state = states[i];
+      WindowTabStateProperties buttonProperties =
+        state == WindowTabState.FOCUSED ? windowTabProperties.getFocusedButtonProperties() :
+          state == WindowTabState.HIGHLIGHTED ? windowTabProperties.getHighlightedButtonProperties() :
+            windowTabProperties.getNormalButtonProperties();
+
+          InternalDockingUtil.updateButtons(buttonInfos,
+              buttons[i],
+              buttonBoxes[i],
+              window,
+              buttonProperties.getMap(),
+              changes);
+
+          buttonBoxes[i].setDirection((state == WindowTabState.NORMAL ?
+                                                                       getProperties().getNormalProperties() :
+                                                                         getProperties().getHighlightedProperties()).getDirection());
+    }
+
+    Direction dir = getProperties().getHighlightedProperties().getDirection();
+    highlightedFocusedPanel.setDirection(dir);
+    customComponents.setDirection(dir);
+  }
+
+  DockingWindow getWindow() {
+    return window;
+  }
+
+  void windowTitleChanged() {
+    setText(getWindow().getTitle());
+    setIcon(getWindow().getIcon());
+  }
+
+  public String toString() {
+    return window != null ? window.toString() : null;
+  }
+
+  void setContentComponent(Component component) {
+    ((SimplePanel) getContentComponent()).setComponent(component);
+  }
+
+  java.util.List getCustomTabComponentsList() {
+    if (tabComponentsList == null)
+      tabComponentsList = new ContainerList(customComponents);
+
+    return tabComponentsList;
+  }
+}
diff --git a/src/net/infonode/docking/WindowTabState.java b/src/net/infonode/docking/WindowTabState.java
new file mode 100644
index 0000000..a215d14
--- /dev/null
+++ b/src/net/infonode/docking/WindowTabState.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowTabState.java,v 1.4 2004/11/19 16:02:09 jesper Exp $
+package net.infonode.docking;
+
+import net.infonode.util.Enum;
+
+/**
+ * The states that a window tab can be in.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+final class WindowTabState extends Enum {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * Normal state means that the tab is not highlighted or focused.
+   */
+  static final WindowTabState NORMAL = new WindowTabState(0, "Normal");
+
+  /**
+   * Highlighted state occurs when the tab is selected or otherwise highlighted.
+   */
+  static final WindowTabState HIGHLIGHTED = new WindowTabState(1, "Highlighted");
+
+  /**
+   * Focused state is when the window that the tab is connected to contains the focus owner.
+   */
+  static final WindowTabState FOCUSED = new WindowTabState(2, "Focused");
+
+  private static final WindowTabState[] STATES = {NORMAL, HIGHLIGHTED, FOCUSED};
+
+  WindowTabState(int value, String name) {
+    super(value, name);
+  }
+
+  static WindowTabState[] getStates() {
+    return (WindowTabState[]) STATES.clone();
+  }
+
+  static int getStateCount() {
+    return STATES.length;
+  }
+}
diff --git a/src/net/infonode/docking/action/CloseOthersWindowAction.java b/src/net/infonode/docking/action/CloseOthersWindowAction.java
new file mode 100644
index 0000000..f46f99c
--- /dev/null
+++ b/src/net/infonode/docking/action/CloseOthersWindowAction.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: CloseOthersWindowAction.java,v 1.2 2005/05/20 14:48:12 johan Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.AbstractTabWindow;
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.OperationAbortedException;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.gui.icon.button.CloseIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Closes all tabs (with abort possibility) except the one belonging to the window the action is performed upon in
+ * the AbstractTabWindow parent of the window.
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.2 $
+ * @since IDW 1.4.0
+ */
+public class CloseOthersWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class
+   */
+  public static final CloseOthersWindowAction INSTANCE = new CloseOthersWindowAction();
+
+  private static final Icon icon = new CloseIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private CloseOthersWindowAction() {
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  public String getName() {
+    return "Close Others";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return window.getWindowParent() instanceof AbstractTabWindow;
+  }
+
+  public void perform(DockingWindow window) {
+    if (isPerformable(window)) {
+      AbstractTabWindow tw = (AbstractTabWindow) window.getWindowParent();
+
+      for (int i = 0; i < tw.getChildWindowCount();) {
+        if (tw.getChildWindow(i) != window && tw.getChildWindow(i).isClosable()) {
+          try {
+            tw.getChildWindow(i).closeWithAbort();
+          }
+          catch (OperationAbortedException e) {
+            i++;
+          }
+        }
+        else
+          i++;
+      }
+    }
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/CloseWindowAction.java b/src/net/infonode/docking/action/CloseWindowAction.java
new file mode 100644
index 0000000..c210da4
--- /dev/null
+++ b/src/net/infonode/docking/action/CloseWindowAction.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: CloseWindowAction.java,v 1.5 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.gui.icon.button.CloseIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * <p>
+ * Closes a window using the {@link DockingWindow#close()} method.
+ * </p>
+ * <p>
+ * In a GUI, you would typically use {@link CloseWithAbortWindowAction} instead of this class.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ * @since IDW 1.3.0
+ */
+public final class CloseWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class
+   */
+  public static final CloseWindowAction INSTANCE = new CloseWindowAction();
+
+  private static final Icon icon = new CloseIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private CloseWindowAction() {
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  public String getName() {
+    return "Close";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return window.isClosable();
+  }
+
+  public void perform(DockingWindow window) {
+    if (isPerformable(window))
+      window.close();
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/CloseWithAbortWindowAction.java b/src/net/infonode/docking/action/CloseWithAbortWindowAction.java
new file mode 100644
index 0000000..8e45b36
--- /dev/null
+++ b/src/net/infonode/docking/action/CloseWithAbortWindowAction.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: CloseWithAbortWindowAction.java,v 1.5 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.OperationAbortedException;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Closes a window using the {@link DockingWindow#closeWithAbort()} method.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ * @since IDW 1.3.0
+ */
+public final class CloseWithAbortWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final CloseWithAbortWindowAction INSTANCE = new CloseWithAbortWindowAction();
+
+  private CloseWithAbortWindowAction() {
+  }
+
+  public String getName() {
+    return CloseWindowAction.INSTANCE.getName();
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return window.isClosable();
+  }
+
+  public void perform(DockingWindow window) {
+    try {
+      if (isPerformable(window))
+        window.closeWithAbort();
+    }
+    catch (OperationAbortedException e) {
+      // Ignore
+    }
+  }
+
+  public Icon getIcon() {
+    return CloseWindowAction.INSTANCE.getIcon();
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/DockWindowAction.java b/src/net/infonode/docking/action/DockWindowAction.java
new file mode 100644
index 0000000..599fa3c
--- /dev/null
+++ b/src/net/infonode/docking/action/DockWindowAction.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockWindowAction.java,v 1.5 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.gui.icon.button.DockIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Docks a window using the {@link DockingWindow#dock()} method.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ * @since IDW 1.4.0
+ */
+public class DockWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class
+   */
+  public static final DockWindowAction INSTANCE = new DockWindowAction();
+
+  private static final Icon icon = new DockIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private DockWindowAction() {
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  public String getName() {
+    return "Dock";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return window.isDockable() && window.isUndocked();
+  }
+
+  public void perform(DockingWindow window) {
+    if (isPerformable(window)) {
+      window.dock();
+    }
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+}
diff --git a/src/net/infonode/docking/action/DockWithAbortWindowAction.java b/src/net/infonode/docking/action/DockWithAbortWindowAction.java
new file mode 100644
index 0000000..c263b9d
--- /dev/null
+++ b/src/net/infonode/docking/action/DockWithAbortWindowAction.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockWithAbortWindowAction.java,v 1.3 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.OperationAbortedException;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.gui.icon.button.DockIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Docks a window using the {@link DockingWindow#dockWithAbort()} method.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @since IDW 1.4.0
+ */
+public class DockWithAbortWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class
+   */
+  public static final DockWithAbortWindowAction INSTANCE = new DockWithAbortWindowAction();
+
+  private static final Icon icon = new DockIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private DockWithAbortWindowAction() {
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  public String getName() {
+    return DockWindowAction.INSTANCE.getName();
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return DockWindowAction.INSTANCE.isPerformable(window);
+  }
+
+  public void perform(DockingWindow window) {
+    if (isPerformable(window)) {
+      try {
+        window.dockWithAbort();
+      }
+      catch (OperationAbortedException e) {
+        // Ignore
+      }
+    }
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+}
diff --git a/src/net/infonode/docking/action/DockingWindowAction.java b/src/net/infonode/docking/action/DockingWindowAction.java
new file mode 100644
index 0000000..9ef053d
--- /dev/null
+++ b/src/net/infonode/docking/action/DockingWindowAction.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowAction.java,v 1.4 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.gui.action.SimpleAction;
+import net.infonode.gui.icon.IconProvider;
+
+import javax.swing.*;
+import java.io.Serializable;
+
+/**
+ * An action that can be performed on a {@link DockingWindow}. It has a name and an optional icon.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.3.0
+ */
+abstract public class DockingWindowAction implements Serializable, IconProvider {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * Returns the name of this action.
+   *
+   * @return the name of this action
+   */
+  abstract public String getName();
+
+  /**
+   * Performs this action on a window.
+   *
+   * @param window the window on which to perform the action
+   */
+  abstract public void perform(DockingWindow window);
+
+  /**
+   * Returns true if this action is performable on a window.
+   *
+   * @param window the window on which the action will be performed
+   * @return true if this action is performable on the window
+   */
+  abstract public boolean isPerformable(DockingWindow window);
+
+  /**
+   * Creates a simple action that performs this action on a window.
+   *
+   * @param window the window on which to perform the action
+   * @return the action that performs this action on a window.
+   */
+  public SimpleAction getAction(final DockingWindow window) {
+    return new SimpleAction() {
+      public String getName() {
+        return DockingWindowAction.this.getName();
+      }
+
+      public boolean isEnabled() {
+        return DockingWindowAction.this.isPerformable(window);
+      }
+
+      public void perform() {
+        DockingWindowAction.this.perform(window);
+      }
+
+      public Icon getIcon() {
+        return DockingWindowAction.this.getIcon();
+      }
+    };
+  }
+
+  /**
+   * Returns the optional icon of this action.
+   *
+   * @return the optional icon of this action, null if there is no icon
+   */
+  public Icon getIcon() {
+    return null;
+  }
+
+  public String toString() {
+    return getName();
+  }
+
+}
diff --git a/src/net/infonode/docking/action/DockingWindowActionProperty.java b/src/net/infonode/docking/action/DockingWindowActionProperty.java
new file mode 100644
index 0000000..3153ad4
--- /dev/null
+++ b/src/net/infonode/docking/action/DockingWindowActionProperty.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowActionProperty.java,v 1.4 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A property that has a {@link DockingWindowAction} object as value.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.3.0
+ */
+public class DockingWindowActionProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public DockingWindowActionProperty(PropertyGroup group, String name, String description,
+                                     PropertyValueHandler valueHandler) {
+    super(group, name, DockingWindowAction.class, description, valueHandler);
+  }
+
+  /**
+   * Gets the {@link DockingWindowAction} value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the {@link DockingWindowAction} value
+   */
+  public DockingWindowAction get(Object valueContainer) {
+    return (DockingWindowAction) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param action         the value
+   */
+  public void set(Object valueContainer, DockingWindowAction action) {
+    setValue(valueContainer, action);
+  }
+}
diff --git a/src/net/infonode/docking/action/MaximizeWindowAction.java b/src/net/infonode/docking/action/MaximizeWindowAction.java
new file mode 100644
index 0000000..1e85876
--- /dev/null
+++ b/src/net/infonode/docking/action/MaximizeWindowAction.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: MaximizeWindowAction.java,v 1.6 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.TabWindow;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.docking.util.DockingUtil;
+import net.infonode.gui.icon.button.MaximizeIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Maximizes a {@link TabWindow}. Finds the parent {@link TabWindow} if the window this action is performed on is not
+ * a {@link TabWindow}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ * @since IDW 1.3.0
+ */
+public final class MaximizeWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final MaximizeWindowAction INSTANCE = new MaximizeWindowAction();
+
+  private static final Icon icon = new MaximizeIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private MaximizeWindowAction() {
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  public String getName() {
+    return "Maximize";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    if (!window.isMaximizable())
+      return false;
+
+    TabWindow tabWindow = DockingUtil.getTabWindowFor(window);
+    return tabWindow != null && !tabWindow.isMaximized() && tabWindow.isMaximizable();
+  }
+
+  public void perform(DockingWindow window) {
+    TabWindow tabWindow = DockingUtil.getTabWindowFor(window);
+
+    if (tabWindow != null && !tabWindow.isMaximized() && tabWindow.isMaximizable())
+      tabWindow.maximize();
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/MaximizeWithAbortWindowAction.java b/src/net/infonode/docking/action/MaximizeWithAbortWindowAction.java
new file mode 100644
index 0000000..fab249e
--- /dev/null
+++ b/src/net/infonode/docking/action/MaximizeWithAbortWindowAction.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: MaximizeWithAbortWindowAction.java,v 1.2 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.OperationAbortedException;
+import net.infonode.docking.TabWindow;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.docking.util.DockingUtil;
+import net.infonode.gui.icon.button.MaximizeIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Maximizes a {@link TabWindow}. Finds the parent {@link TabWindow} if the window this action is performed on is not
+ * a {@link TabWindow}. The action calls {@link net.infonode.docking.DockingWindow#maximizeWithAbort()}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ * @since IDW 1.4.0
+ */
+public final class MaximizeWithAbortWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final MaximizeWithAbortWindowAction INSTANCE = new MaximizeWithAbortWindowAction();
+
+  private static final Icon icon = new MaximizeIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private MaximizeWithAbortWindowAction() {
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  public String getName() {
+    return "Maximize";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    if (!window.isMaximizable())
+      return false;
+
+    TabWindow tabWindow = DockingUtil.getTabWindowFor(window);
+    return tabWindow != null && !tabWindow.isMaximized() && tabWindow.isMaximizable();
+  }
+
+  public void perform(DockingWindow window) {
+    try {
+      TabWindow tabWindow = DockingUtil.getTabWindowFor(window);
+
+      if (tabWindow != null && !tabWindow.isMaximized() && tabWindow.isMaximizable())
+        tabWindow.maximizeWithAbort();
+    }
+    catch (OperationAbortedException e) {
+      // Ignore
+    }
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/MinimizeWithAbortWindowAction.java b/src/net/infonode/docking/action/MinimizeWithAbortWindowAction.java
new file mode 100644
index 0000000..da1b563
--- /dev/null
+++ b/src/net/infonode/docking/action/MinimizeWithAbortWindowAction.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: MinimizeWithAbortWindowAction.java,v 1.2 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.OperationAbortedException;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.gui.icon.button.MinimizeIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Minimizes a window. The action calls {@link net.infonode.docking.DockingWindow#minimizeWithAbort()}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ * @since IDW 1.4.0
+ */
+public final class MinimizeWithAbortWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final MinimizeWithAbortWindowAction INSTANCE = new MinimizeWithAbortWindowAction();
+
+  private static final Icon icon = new MinimizeIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private MinimizeWithAbortWindowAction() {
+  }
+
+  public String getName() {
+    return "Minimize";
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return window != null && !window.isMinimized() && window.isMinimizable();
+  }
+
+  public void perform(DockingWindow window) {
+    try {
+      if (isPerformable(window))
+        window.minimizeWithAbort();
+    }
+    catch (OperationAbortedException e) {
+      // Ignore
+    }
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/NullWindowAction.java b/src/net/infonode/docking/action/NullWindowAction.java
new file mode 100644
index 0000000..21a992b
--- /dev/null
+++ b/src/net/infonode/docking/action/NullWindowAction.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: NullWindowAction.java,v 1.4 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+
+import java.io.ObjectStreamException;
+
+/**
+ * Does nothing.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.3.0
+ */
+public class NullWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final NullWindowAction INSTANCE = new NullWindowAction();
+
+  private NullWindowAction() {
+  }
+
+  public String getName() {
+    return "Null";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return true;
+  }
+
+  public void perform(DockingWindow window) {
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/RestoreFocusWindowAction.java b/src/net/infonode/docking/action/RestoreFocusWindowAction.java
new file mode 100644
index 0000000..ef42bc6
--- /dev/null
+++ b/src/net/infonode/docking/action/RestoreFocusWindowAction.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RestoreFocusWindowAction.java,v 1.5 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+
+import java.io.ObjectStreamException;
+
+/**
+ * Uses the {@link DockingWindow#restoreFocus()} method to restore focus to the last focus owner
+ * that inside a window.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ * @since IDW 1.3.0
+ */
+public final class RestoreFocusWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final RestoreFocusWindowAction INSTANCE = new RestoreFocusWindowAction();
+
+  private RestoreFocusWindowAction() {
+  }
+
+  public String getName() {
+    return "Restore Focus";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return true;
+  }
+
+  public void perform(DockingWindow window) {
+    window.restoreFocus();
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/RestoreParentWindowAction.java b/src/net/infonode/docking/action/RestoreParentWindowAction.java
new file mode 100644
index 0000000..2141fc3
--- /dev/null
+++ b/src/net/infonode/docking/action/RestoreParentWindowAction.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RestoreParentWindowAction.java,v 1.5 2005/12/03 14:34:33 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.TabWindow;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.docking.util.DockingUtil;
+import net.infonode.gui.icon.button.RestoreIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Restores a window using the {@link DockingWindow#restore()} method. If the parent window is a {@link TabWindow}
+ * which is maximized, it is restored.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ * @since IDW 1.3.0
+ */
+public final class RestoreParentWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final RestoreParentWindowAction INSTANCE = new RestoreParentWindowAction();
+
+  private static final Icon icon = new RestoreIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private RestoreParentWindowAction() {
+  }
+
+  public String getName() {
+    return "Restore";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    if (window.isMinimized() && window.isRestorable())
+      return true;
+    else {
+      TabWindow tabWindow = DockingUtil.getTabWindowFor(window);
+      return tabWindow != null && tabWindow.isMaximized() && tabWindow.isRestorable();
+    }
+  }
+
+  public void perform(DockingWindow window) {
+    if (window != null && window.isMinimized())
+      restore(window);
+    else {
+      TabWindow tabWindow = DockingUtil.getTabWindowFor(window);
+
+      if (tabWindow != null && tabWindow.isMaximized())
+        restore(tabWindow);
+    }
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  private static void restore(DockingWindow window) {
+    if (window != null && window.isRestorable())
+      window.restore();
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/RestoreParentWithAbortWindowAction.java b/src/net/infonode/docking/action/RestoreParentWithAbortWindowAction.java
new file mode 100644
index 0000000..68b8d2b
--- /dev/null
+++ b/src/net/infonode/docking/action/RestoreParentWithAbortWindowAction.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RestoreParentWithAbortWindowAction.java,v 1.3 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.OperationAbortedException;
+import net.infonode.docking.TabWindow;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.docking.util.DockingUtil;
+import net.infonode.gui.icon.button.RestoreIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Restores a window using the {@link DockingWindow#restore()} method. If the parent window is a {@link TabWindow}
+ * which is maximized, it is restored. The action calls {@link net.infonode.docking.DockingWindow#restoreWithAbort()}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @since IDW 1.4.0
+ */
+public final class RestoreParentWithAbortWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final RestoreParentWithAbortWindowAction INSTANCE = new RestoreParentWithAbortWindowAction();
+
+  private static final Icon icon = new RestoreIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private RestoreParentWithAbortWindowAction() {
+  }
+
+  public String getName() {
+    return "Restore";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    if (window.isMinimized() && window.isRestorable())
+      return true;
+    else {
+      TabWindow tabWindow = DockingUtil.getTabWindowFor(window);
+      return tabWindow != null && tabWindow.isMaximized() && tabWindow.isRestorable();
+    }
+  }
+
+  public void perform(DockingWindow window) {
+    if (window != null && window.isMinimized())
+      restore(window);
+    else {
+      TabWindow tabWindow = DockingUtil.getTabWindowFor(window);
+
+      if (tabWindow != null && tabWindow.isMaximized())
+        restore(tabWindow);
+    }
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  private static void restore(DockingWindow window) {
+    try {
+      if (window != null && window.isRestorable())
+        window.restoreWithAbort();
+    }
+    catch (OperationAbortedException e) {
+      // Ignore
+    }
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/RestoreViewWithAbortTitleBarAction.java b/src/net/infonode/docking/action/RestoreViewWithAbortTitleBarAction.java
new file mode 100644
index 0000000..7b08903
--- /dev/null
+++ b/src/net/infonode/docking/action/RestoreViewWithAbortTitleBarAction.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RestoreViewWithAbortTitleBarAction.java,v 1.3 2007/01/28 21:25:10 jesper Exp $
+
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.OperationAbortedException;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.gui.icon.button.RestoreIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * <p>
+ * Restores a window using the {@link net.infonode.docking.DockingWindow#restoreWithAbort()} method.
+ * </p>
+ *
+ * <p>
+ * This action is only meant to be used as restore action on a button on a view's title
+ * bar.
+ * </p>
+ *
+ * @author johan
+ * @version $Revision: 1.3 $
+ * @since IDW 1.4.0
+ */
+public class RestoreViewWithAbortTitleBarAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final RestoreViewWithAbortTitleBarAction INSTANCE = new RestoreViewWithAbortTitleBarAction();
+
+  private static final Icon icon = new RestoreIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private RestoreViewWithAbortTitleBarAction() {
+  }
+
+  public String getName() {
+    return "Restore";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return window != null && (window.isMinimized() || window.isMaximized() ||
+                              (window.getWindowParent() != null && window.getWindowParent()
+                                  .isMaximized() && window.getWindowParent().isRestorable())) && window.isRestorable();
+  }
+
+  public void perform(DockingWindow window) {
+    try {
+      if (window != null && window.isRestorable()) {
+        if (window.isMaximized() || window.isMinimized())
+          window.restoreWithAbort();
+        else {
+          if (window.getWindowParent() != null)
+            window.getWindowParent().restoreWithAbort();
+        }
+      }
+    }
+    catch (OperationAbortedException e) {
+// Ignore
+    }
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+}
diff --git a/src/net/infonode/docking/action/RestoreWithAbortWindowAction.java b/src/net/infonode/docking/action/RestoreWithAbortWindowAction.java
new file mode 100644
index 0000000..b183317
--- /dev/null
+++ b/src/net/infonode/docking/action/RestoreWithAbortWindowAction.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RestoreWithAbortWindowAction.java,v 1.3 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.OperationAbortedException;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.gui.icon.button.RestoreIcon;
+
+import javax.swing.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Restores a window using the {@link DockingWindow#restore()} method. The action calls
+ * {@link net.infonode.docking.DockingWindow#restoreWithAbort()}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @since IDW 1.4.0
+ */
+public class RestoreWithAbortWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final RestoreWithAbortWindowAction INSTANCE = new RestoreWithAbortWindowAction();
+
+  private static final Icon icon = new RestoreIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private RestoreWithAbortWindowAction() {
+  }
+
+  public String getName() {
+    return "Restore";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return window != null && (window.isMinimized() || window.isMaximized()) &&
+           window.isRestorable();// && !window.isUndocked();
+  }
+
+  public void perform(DockingWindow window) {
+    try {
+      if (window != null && window.isRestorable())
+        window.restoreWithAbort();
+    }
+    catch (OperationAbortedException e) {
+      // Ignore
+    }
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/StateDependentWindowAction.java b/src/net/infonode/docking/action/StateDependentWindowAction.java
new file mode 100644
index 0000000..f59727c
--- /dev/null
+++ b/src/net/infonode/docking/action/StateDependentWindowAction.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: StateDependentWindowAction.java,v 1.6 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.TabWindow;
+import net.infonode.docking.util.DockingUtil;
+
+/**
+ * Performs different actions on a window depending on the state of the window.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ * @since IDW 1.3.0
+ */
+public class StateDependentWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * If the window is maximized or minimized it is restored, otherwise it is maximized.
+   */
+  public static final StateDependentWindowAction MAXIMIZE_RESTORE = new StateDependentWindowAction(
+      MaximizeWindowAction.INSTANCE,
+      RestoreParentWindowAction.INSTANCE,
+      RestoreParentWindowAction.INSTANCE);
+
+  /**
+   * If the window is maximized or minimized it is restored, otherwise it is maximized. The operations
+   * can be aborted by a {@link net.infonode.docking.DockingWindowListener}.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final StateDependentWindowAction MAXIMIZE_RESTORE_WITH_ABORT = new StateDependentWindowAction(
+      MaximizeWithAbortWindowAction.INSTANCE,
+      RestoreParentWithAbortWindowAction.INSTANCE,
+      RestoreParentWithAbortWindowAction.INSTANCE);
+
+  private DockingWindowAction normalAction;
+  private DockingWindowAction minimizedAction;
+  private DockingWindowAction maximizedAction;
+
+  /**
+   * Constructor.
+   *
+   * @param normalAction    the action to perform if a window is in normal state
+   * @param minimizedAction the action to perform if a window is minimized
+   * @param maximizedAction the action to perform if a window is maximized
+   */
+  public StateDependentWindowAction(DockingWindowAction normalAction,
+                                    DockingWindowAction minimizedAction,
+                                    DockingWindowAction maximizedAction) {
+    this.normalAction = normalAction;
+    this.minimizedAction = minimizedAction;
+    this.maximizedAction = maximizedAction;
+  }
+
+  public String getName() {
+    return "State Dependent";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return getActionProvider(window).isPerformable(window);
+  }
+
+  public void perform(DockingWindow window) {
+    getActionProvider(window).perform(window);
+  }
+
+  private DockingWindowAction getActionProvider(DockingWindow window) {
+    if (window.isMinimized())
+      return minimizedAction;
+    else {
+      TabWindow tabWindow = DockingUtil.getTabWindowFor(window);
+      return tabWindow != null && tabWindow.isMaximized() ? maximizedAction : normalAction;
+    }
+  }
+
+}
diff --git a/src/net/infonode/docking/action/UndockWindowAction.java b/src/net/infonode/docking/action/UndockWindowAction.java
new file mode 100644
index 0000000..2bc65b1
--- /dev/null
+++ b/src/net/infonode/docking/action/UndockWindowAction.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: UndockWindowAction.java,v 1.8 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.FloatingWindow;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.docking.util.DockingUtil;
+import net.infonode.gui.icon.button.UndockIcon;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Undocks a window using the {@link DockingWindow#undock(Point)} method.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ * @since IDW 1.4.0
+ */
+public class UndockWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class
+   */
+  public static final UndockWindowAction INSTANCE = new UndockWindowAction();
+
+  private static final Icon icon = new UndockIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private UndockWindowAction() {
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  public String getName() {
+    return "Undock";
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    if (window.isUndockable()) {
+      FloatingWindow fw = DockingUtil.getFloatingWindowFor(window);
+      return fw == null || (fw.getChildWindowCount() > 0 && fw.getChildWindow(0) != window && fw.getChildWindow(0)
+          .getChildWindowCount() > 1);
+    }
+
+    return false;
+  }
+
+  public void perform(DockingWindow window) {
+    if (isPerformable(window)) {
+      Point p = window.getLocation();
+      SwingUtilities.convertPointToScreen(p, window.getParent());
+      window.undock(p);
+    }
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+}
diff --git a/src/net/infonode/docking/action/UndockWithAbortWindowAction.java b/src/net/infonode/docking/action/UndockWithAbortWindowAction.java
new file mode 100644
index 0000000..fe8a015
--- /dev/null
+++ b/src/net/infonode/docking/action/UndockWithAbortWindowAction.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: UndockWithAbortWindowAction.java,v 1.3 2005/12/04 13:46:04 jesper Exp $
+
+package net.infonode.docking.action;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.OperationAbortedException;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.gui.icon.button.UndockIcon;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.ObjectStreamException;
+
+/**
+ * Undocks a window using the {@link DockingWindow#undockWithAbort(Point)} method.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @since IDW 1.4.0
+ */
+public class UndockWithAbortWindowAction extends DockingWindowAction {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class
+   */
+  public static final UndockWithAbortWindowAction INSTANCE = new UndockWithAbortWindowAction();
+
+  private static final Icon icon = new UndockIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE);
+
+  private UndockWithAbortWindowAction() {
+  }
+
+  public Icon getIcon() {
+    return icon;
+  }
+
+  public String getName() {
+    return UndockWindowAction.INSTANCE.getName();
+  }
+
+  public boolean isPerformable(DockingWindow window) {
+    return UndockWindowAction.INSTANCE.isPerformable(window);
+  }
+
+  public void perform(DockingWindow window) {
+    if (isPerformable(window)) {
+      Point p = window.getLocation();
+      SwingUtilities.convertPointToScreen(p, window.getParent());
+      try {
+        window.undockWithAbort(p);
+      }
+      catch (OperationAbortedException e) {
+        // Ignore
+      }
+    }
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/action/package.html b/src/net/infonode/docking/action/package.html
new file mode 100644
index 0000000..aa9f246
--- /dev/null
+++ b/src/net/infonode/docking/action/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Action classes for docking windows.
+</body>
+</html>
diff --git a/src/net/infonode/docking/drag/DockingWindowDragSource.java b/src/net/infonode/docking/drag/DockingWindowDragSource.java
new file mode 100644
index 0000000..da1a0bb
--- /dev/null
+++ b/src/net/infonode/docking/drag/DockingWindowDragSource.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowDragSource.java,v 1.8 2009/02/05 15:57:55 jesper Exp $
+package net.infonode.docking.drag;
+
+import javax.swing.JComponent;
+
+import net.infonode.gui.draggable.DraggableComponent;
+import net.infonode.gui.draggable.DraggableComponentAdapter;
+import net.infonode.gui.draggable.DraggableComponentEvent;
+
+/**
+ * Handles the drag and drop of a {@link net.infonode.docking.DockingWindow} triggered by mouse events on a
+ * {@link JComponent}. {@link DockingWindowDragSource} handles drag abort with the right mouse button and
+ * the key set in the {@link net.infonode.docking.properties.RootWindowProperties#ABORT_DRAG_KEY} property of the
+ * {@link net.infonode.docking.RootWindow} which is the drop target.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ * @since IDW 1.3.0
+ */
+public class DockingWindowDragSource {
+  private final DraggableComponent draggableComponent;
+  private DockingWindowDragger dragger;
+  //  private Point startPoint;
+
+  /**
+   * Constructor.
+   *
+   * @param component       the component on which to listen to mouse events that affects the drag and drop of a
+   *                        {@link net.infonode.docking.DockingWindow}
+   * @param draggerProvider provides the {@link DockingWindowDragger} when the drag operation begins, typically
+   *                        this provider gets the dragger by calling
+   *                        {@link net.infonode.docking.DockingWindow#startDrag(net.infonode.docking.RootWindow)}
+   *                        on the window which should be dragged
+   */
+  public DockingWindowDragSource(JComponent component,
+                                 final DockingWindowDraggerProvider draggerProvider) {
+    draggableComponent = new DraggableComponent(component);
+    draggableComponent.setReorderEnabled(false);
+    draggableComponent.setEnableInsideDrag(true);
+
+    draggableComponent.addListener(new DraggableComponentAdapter() {
+      public void dragAborted(DraggableComponentEvent event) {
+        abortDrag();
+      }
+
+      public void dragged(DraggableComponentEvent event) {
+        if (dragger == null) {
+          //          startPoint = event.getMouseEvent().getPoint();
+          dragger = draggerProvider.getDragger(event.getMouseEvent());
+
+          if (dragger == null) {
+            draggableComponent.abortDrag();
+            return;
+          }
+
+          draggableComponent.setAbortDragKeyCode(dragger.getDropTarget().getRootWindowProperties().getAbortDragKey());
+        }
+
+        /*if (startPoint != null &&
+            Math.abs(startPoint.x - event.getMouseEvent().getX()) +
+            Math.abs(startPoint.y - event.getMouseEvent().getY()) < 16)
+          return;*/
+
+        //        startPoint = null;
+        dragger.dragWindow(event.getMouseEvent());
+      }
+
+      public void dropped(DraggableComponentEvent event) {
+        if (dragger != null) {
+          dragger.dropWindow(event.getMouseEvent());
+          dragger = null;
+          //startPoint = null;
+        }
+      }
+    });
+  }
+
+  /**
+   * Aborts the currect drag operation.
+   */
+  public void abortDrag() {
+    if (dragger != null)
+      dragger.abortDrag();
+
+    dragger = null;
+    //    startPoint = null;
+  }
+
+}
diff --git a/src/net/infonode/docking/drag/DockingWindowDragger.java b/src/net/infonode/docking/drag/DockingWindowDragger.java
new file mode 100644
index 0000000..5873b7b
--- /dev/null
+++ b/src/net/infonode/docking/drag/DockingWindowDragger.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowDragger.java,v 1.6 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.drag;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.RootWindow;
+
+import java.awt.event.MouseEvent;
+
+/**
+ * Handles the drag and drop of a {@link DockingWindow}. Note the the drag operation MUST be terminated using either
+ * {@link #abortDrag()} or {@link #dropWindow(MouseEvent)}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ * @since IDW 1.3.0
+ */
+public interface DockingWindowDragger {
+  /**
+   * Returns the {@link RootWindow} where the window can be dropped.
+   *
+   * @return the {@link RootWindow} where the window can be dropped
+   */
+  RootWindow getDropTarget();
+
+  /**
+   * The window that is dragged and dropped.
+   *
+   * @return the window that is dragged and dropped.
+   */
+  DockingWindow getDragWindow();
+
+  /**
+   * Drags the window to a new location. The location is relative to the {@link RootWindow} in where it should be
+   * dropped, see {@link #getDropTarget()}.
+   *
+   * @param mouseEvent the mouse event that caused the drag
+   */
+  void dragWindow(MouseEvent mouseEvent);
+
+  /**
+   * Aborts this drag operation.
+   */
+  void abortDrag();
+
+  /**
+   * Drops the window at the specified location.
+   *
+   * @param mouseEvent the mouse event that caused the drop
+   */
+  void dropWindow(MouseEvent mouseEvent);
+}
diff --git a/src/net/infonode/docking/drag/DockingWindowDraggerProvider.java b/src/net/infonode/docking/drag/DockingWindowDraggerProvider.java
new file mode 100644
index 0000000..9df2e1c
--- /dev/null
+++ b/src/net/infonode/docking/drag/DockingWindowDraggerProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowDraggerProvider.java,v 1.3 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.drag;
+
+import java.awt.event.MouseEvent;
+
+/**
+ * Provides a {@link DockingWindowDragger}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @since IDW 1.3.0
+ */
+public interface DockingWindowDraggerProvider {
+  /**
+   * Returns a {@link DockingWindowDragger}.
+   *
+   * @param mouseEvent the mouse event that started the drag
+   * @return the {@link DockingWindowDragger}
+   */
+  DockingWindowDragger getDragger(MouseEvent mouseEvent);
+}
diff --git a/src/net/infonode/docking/drag/package.html b/src/net/infonode/docking/drag/package.html
new file mode 100644
index 0000000..0a09903
--- /dev/null
+++ b/src/net/infonode/docking/drag/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Classes for docking windows drag and drop functionality.
+</body>
+</html>
diff --git a/src/net/infonode/docking/drop/AcceptAllDropFilter.java b/src/net/infonode/docking/drop/AcceptAllDropFilter.java
new file mode 100644
index 0000000..8c5c81f
--- /dev/null
+++ b/src/net/infonode/docking/drop/AcceptAllDropFilter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AcceptAllDropFilter.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.drop;
+
+/**
+ * A {@link net.infonode.docking.drop.DropFilter} that will accept drop of any
+ * window.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.4.0
+ */
+public class AcceptAllDropFilter implements DropFilter {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class
+   */
+  public static AcceptAllDropFilter INSTANCE = new AcceptAllDropFilter();
+
+  private AcceptAllDropFilter() {
+  }
+
+  public boolean acceptDrop(DropInfo dropInfo) {
+    return true;
+  }
+}
diff --git a/src/net/infonode/docking/drop/ChildDropInfo.java b/src/net/infonode/docking/drop/ChildDropInfo.java
new file mode 100644
index 0000000..b865a92
--- /dev/null
+++ b/src/net/infonode/docking/drop/ChildDropInfo.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ChildDropInfo.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.drop;
+
+import net.infonode.docking.DockingWindow;
+
+import java.awt.*;
+
+/**
+ * <p>
+ * Information about an ongoing child drop i.e. when a child window
+ * of the drop window is asked if it accepts the drop.
+ * </p>
+ *
+ * <p>
+ * A drop is started at the root of the window tree and then passed
+ * down the tree to the appropriate child. Rejecting a child drop
+ * makes it possible to disable all types of drops in that subtree
+ * of the drop window.
+ * </p>
+ *
+ * <p>
+ * A child drop can be performed on windows that can have children,
+ * i.e. {@link net.infonode.docking.RootWindow},
+ * {@link net.infonode.docking.SplitWindow},
+ * {@link net.infonode.docking.TabWindow},
+ * {@link net.infonode.docking.WindowBar} or
+ * {@link net.infonode.docking.FloatingWindow}.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.4.0
+ */
+public class ChildDropInfo extends DropInfo {
+  private DockingWindow childWindow;
+
+  public ChildDropInfo(DockingWindow window, DockingWindow dropWindow, Point point, DockingWindow childWindow) {
+    super(window, dropWindow, point);
+    this.childWindow = childWindow;
+  }
+
+  /**
+   * Returns the current child window in the drop window that will
+   * be asked if it accepts any type of drops.
+   *
+   * @return the child window
+   */
+  public DockingWindow getChildWindow() {
+    return childWindow;
+  }
+}
diff --git a/src/net/infonode/docking/drop/DropFilter.java b/src/net/infonode/docking/drop/DropFilter.java
new file mode 100644
index 0000000..f481184
--- /dev/null
+++ b/src/net/infonode/docking/drop/DropFilter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DropFilter.java,v 1.4 2007/01/07 19:11:20 jesper Exp $
+package net.infonode.docking.drop;
+
+/**
+ * <p>
+ * Interface for filtering drops when a drag and drop is in progress.
+ * </p>
+ *
+ * <p>
+ * There are 4 kinds of drop types, see
+ * {@link net.infonode.docking.drop.SplitDropInfo},
+ * {@link net.infonode.docking.drop.ChildDropInfo},
+ * {@link net.infonode.docking.drop.InteriorDropInfo} and
+ * {@link net.infonode.docking.drop.InsertTabDropInfo}.
+ * </p>
+ *
+ * <p>
+ * A drop filter is used to filter drops. The filter may decide if a
+ * drop of a window into another window is to be accepted or not. This
+ * makes it possible to tailor the drop behavior of a
+ * {@link net.infonode.docking.DockingWindow}. The window (called drop window)
+ * into which another window is beeing dropped is asked if it will accept a
+ * drop of that window. The filter is asked continuously during a drag operation
+ * i.e. it may be asked many times during a drag operation.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.4.0
+ */
+public interface DropFilter {
+
+  /**
+   * Return true if the drop should be accepted, otherwise false.
+   *
+   * @param dropInfo information about the current drop
+   * @return true if drop is to be accepted, otherwise false
+   */
+  boolean acceptDrop(DropInfo dropInfo);
+}
diff --git a/src/net/infonode/docking/drop/DropFilterProperty.java b/src/net/infonode/docking/drop/DropFilterProperty.java
new file mode 100644
index 0000000..a20eca3
--- /dev/null
+++ b/src/net/infonode/docking/drop/DropFilterProperty.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DropFilterProperty.java,v 1.3 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.drop;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A property that has a {@link net.infonode.docking.drop.DropFilter} object as value.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @since IDW 1.4.0
+ */
+public class DropFilterProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public DropFilterProperty(PropertyGroup group, String name, String description,
+                            PropertyValueHandler valueHandler) {
+    super(group, name, DropFilter.class, description, valueHandler);
+  }
+
+  /**
+   * Gets the {@link DropFilter} value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the {@link DropFilter} value
+   */
+  public DropFilter get(Object valueContainer) {
+    return (DropFilter) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param filter         the value
+   */
+  public void set(Object valueContainer, DropFilter filter) {
+    setValue(valueContainer, filter);
+  }
+}
diff --git a/src/net/infonode/docking/drop/DropInfo.java b/src/net/infonode/docking/drop/DropInfo.java
new file mode 100644
index 0000000..a6797ec
--- /dev/null
+++ b/src/net/infonode/docking/drop/DropInfo.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DropInfo.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.drop;
+
+import net.infonode.docking.DockingWindow;
+
+import java.awt.*;
+
+/**
+ * <p>
+ * Super class for all drop infos
+ * </p>
+ *
+ * <p>
+ * A drop info is passed on to a {@link net.infonode.docking.drop.DropFilter}
+ * when a drag and drop operation is in progress. This makes it possible for
+ * the drop filter to decide if a drop is accepted or.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.4.0
+ */
+public class DropInfo {
+  private DockingWindow window;
+  private DockingWindow dropWindow;
+  private Point point;
+
+  DropInfo(DockingWindow window, DockingWindow dropWindow, Point point) {
+    this.window = window;
+    this.dropWindow = dropWindow;
+    this.point = point;
+  }
+
+  /**
+   * Returns the window that is beeing dragged, i.e. the window that could be
+   * dropped.
+   *
+   * @return the window beeing dragged
+   */
+  public DockingWindow getWindow() {
+    return window;
+  }
+
+  /**
+   * Returns the window that is asked (via the {@link DropFilter} if a drop of
+   * the dragged window is accepted.
+   *
+   * @return the window that is asked if a drop of the dragged window is accepted
+   */
+  public DockingWindow getDropWindow() {
+    return dropWindow;
+  }
+
+  /**
+   * Returns the current mouse point relative to the drop window.
+   *
+   * @return the mouse point relative to the drop window
+   */
+  public Point getPoint() {
+    return point;
+  }
+}
diff --git a/src/net/infonode/docking/drop/InsertTabDropInfo.java b/src/net/infonode/docking/drop/InsertTabDropInfo.java
new file mode 100644
index 0000000..a3f65ab
--- /dev/null
+++ b/src/net/infonode/docking/drop/InsertTabDropInfo.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InsertTabDropInfo.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.drop;
+
+import net.infonode.docking.DockingWindow;
+
+import java.awt.*;
+
+/**
+ * <p>
+ * Information about an insert tab drop.
+ * </p>
+ *
+ * <p>
+ * An insert tab drop is performed when a window is dragged and ropped
+ * into the tab area of a {@link net.infonode.docking.TabWindow} or a
+ * {@link net.infonode.docking.WindowBar}.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.4.0
+ */
+public class InsertTabDropInfo extends DropInfo {
+  public InsertTabDropInfo(DockingWindow window, DockingWindow dropWindow, Point point) {
+    super(window, dropWindow, point);
+  }
+}
diff --git a/src/net/infonode/docking/drop/InteriorDropInfo.java b/src/net/infonode/docking/drop/InteriorDropInfo.java
new file mode 100644
index 0000000..6d1a3b7
--- /dev/null
+++ b/src/net/infonode/docking/drop/InteriorDropInfo.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InteriorDropInfo.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.drop;
+
+import net.infonode.docking.DockingWindow;
+
+import java.awt.*;
+
+/**
+ * <p>
+ * Information about a drop into a window.
+ * </p>
+ *
+ * <p>
+ * This drop type is performed on a {@link net.infonode.docking.RootWindow}
+ * or a {@link net.infonode.docking.FloatingWindow} when they don't have any
+ * top-level windows i.e. they appear empty. It is also perfomed on a
+ * {@link net.infonode.docking.SplitWindow} when the drop will result in a
+ * replace of the split window with a tab window containing the left window,
+ * right window and the window that was dropped.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.4.0
+ */
+public class InteriorDropInfo extends DropInfo {
+  public InteriorDropInfo(DockingWindow window, DockingWindow dropWindow, Point point) {
+    super(window, dropWindow, point);
+  }
+}
diff --git a/src/net/infonode/docking/drop/RejectAllDropFilter.java b/src/net/infonode/docking/drop/RejectAllDropFilter.java
new file mode 100644
index 0000000..cba25de
--- /dev/null
+++ b/src/net/infonode/docking/drop/RejectAllDropFilter.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RejectAllDropFilter.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.drop;
+
+/**
+ * A {@link net.infonode.docking.drop.DropFilter} that will reject drop of any
+ * window.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.4.0
+ */
+public class RejectAllDropFilter implements DropFilter {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class
+   */
+  public static RejectAllDropFilter INSTANCE = new RejectAllDropFilter();
+
+  private RejectAllDropFilter() {
+  }
+
+  public boolean acceptDrop(DropInfo dropInfo) {
+    return false;
+  }
+}
diff --git a/src/net/infonode/docking/drop/SplitDropInfo.java b/src/net/infonode/docking/drop/SplitDropInfo.java
new file mode 100644
index 0000000..3106777
--- /dev/null
+++ b/src/net/infonode/docking/drop/SplitDropInfo.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SplitDropInfo.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.drop;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * <p>
+ * Information about an ongoing split drop i.e. a drop that would result
+ * in a split of the drop window.
+ * </p>
+ *
+ * <p>
+ * A split drop can be performed on a {@link net.infonode.docking.SplitWindow},
+ * {@link net.infonode.docking.TabWindow} or a {@link net.infonode.docking.View}.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.4.0
+ */
+public class SplitDropInfo extends DropInfo {
+  private Direction splitDirection;
+
+  public SplitDropInfo(DockingWindow window, DockingWindow dropWindow, Point point, Direction splitDirection) {
+    super(window, dropWindow, point);
+    this.splitDirection = splitDirection;
+  }
+
+  /**
+   * Returns the current split direction i.e. in what direction the drop window
+   * should be split.
+   *
+   * @return the split direction
+   */
+  public Direction getSplitDirection() {
+    return splitDirection;
+  }
+}
diff --git a/src/net/infonode/docking/drop/package.html b/src/net/infonode/docking/drop/package.html
new file mode 100644
index 0000000..30d55d5
--- /dev/null
+++ b/src/net/infonode/docking/drop/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Drop filter classes for docking windows.
+</body>
+</html>
diff --git a/src/net/infonode/docking/info/Info.java b/src/net/infonode/docking/info/Info.java
new file mode 100644
index 0000000..17a3fa6
--- /dev/null
+++ b/src/net/infonode/docking/info/Info.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Info.java,v 1.6 2004/09/22 14:31:39 jesper Exp $
+package net.infonode.docking.info;
+
+import net.infonode.docking.DockingWindowsReleaseInfo;
+import net.infonode.gui.ReleaseInfoDialog;
+import net.infonode.gui.laf.InfoNodeLookAndFeelReleaseInfo;
+import net.infonode.tabbedpanel.TabbedPanelReleaseInfo;
+import net.infonode.util.ReleaseInfo;
+
+/**
+ * Program that shows InfoNode Docking Windows release information in a dialog.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class Info {
+  private Info() {
+  }
+
+  public static final void main(String[] args) {
+    ReleaseInfoDialog.showDialog(new ReleaseInfo[]{DockingWindowsReleaseInfo.getReleaseInfo(),
+                                                   TabbedPanelReleaseInfo.getReleaseInfo(),
+                                                   InfoNodeLookAndFeelReleaseInfo.getReleaseInfo()},
+                                 null);
+    System.exit(0);
+  }
+}
diff --git a/src/net/infonode/docking/internal/HeavyWeightContainer.java b/src/net/infonode/docking/internal/HeavyWeightContainer.java
new file mode 100644
index 0000000..6ecb722
--- /dev/null
+++ b/src/net/infonode/docking/internal/HeavyWeightContainer.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: HeavyWeightContainer.java,v 1.5 2005/12/04 13:46:04 jesper Exp $
+
+package net.infonode.docking.internal;
+
+import java.awt.*;
+
+/**
+ * @author johan
+ */
+public class HeavyWeightContainer extends Panel {
+  private Image bufferImage;
+  private boolean doubleBuffer = false;
+
+  public HeavyWeightContainer(Component c) {
+    this(c, false);
+  }
+
+  public HeavyWeightContainer(Component c, boolean doubleBuffer) {
+    super(new BorderLayout());
+    this.doubleBuffer = doubleBuffer;
+    add(c, BorderLayout.CENTER);
+  }
+
+  public void invalidate() {
+    super.invalidate();
+    bufferImage = null;
+  }
+
+  public void update(Graphics g) {
+    if (doubleBuffer)
+      paint(g);
+    else
+      super.update(g);
+  }
+
+  public boolean isDoubleBuffered() {
+    return doubleBuffer;
+  }
+
+  public void paint(Graphics g) {
+    if (doubleBuffer) {
+      if (bufferImage == null)
+        bufferImage = createImage(getWidth(), getHeight());
+
+      Graphics g2 = bufferImage.getGraphics();
+      g2.setColor(getBackground());
+      g2.fillRect(0, 0, getWidth(), getHeight());
+      super.paint(g2);
+
+      g.drawImage(bufferImage, 0, 0, null);
+      g2.dispose();
+    }
+    else {
+      super.paint(g);
+    }
+  }
+}
diff --git a/src/net/infonode/docking/internal/HeavyWeightDragRectangle.java b/src/net/infonode/docking/internal/HeavyWeightDragRectangle.java
new file mode 100644
index 0000000..7b6d4d0
--- /dev/null
+++ b/src/net/infonode/docking/internal/HeavyWeightDragRectangle.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: HeavyWeightDragRectangle.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.internal;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author johan
+ */
+public class HeavyWeightDragRectangle extends JPanel {
+  private int width = 4;
+
+  private Canvas northCanvas = new Canvas() {
+    public Dimension getPreferredSize() {
+      return new Dimension(width, width);
+    }
+  };
+  private Canvas southCanvas = new Canvas() {
+    public Dimension getPreferredSize() {
+      return new Dimension(width, width);
+    }
+  };
+  private Canvas eastCanvas = new Canvas() {
+    public Dimension getPreferredSize() {
+      return new Dimension(width, width);
+    }
+  };
+  private Canvas westCanvas = new Canvas() {
+    public Dimension getPreferredSize() {
+      return new Dimension(width, width);
+    }
+  };
+
+  public HeavyWeightDragRectangle() {
+    super(new BorderLayout());
+    setOpaque(false);
+
+    add(northCanvas, BorderLayout.NORTH);
+    add(southCanvas, BorderLayout.SOUTH);
+    add(westCanvas, BorderLayout.WEST);
+    add(eastCanvas, BorderLayout.EAST);
+
+    setColor(Color.BLACK);
+  }
+
+  public void setBounds(int x, int y, int width, int height) {
+    super.setBounds(x, y, width, height);
+    revalidate();
+  }
+
+  public void setBorderWidth(int width) {
+    this.width = width;
+
+    revalidate();
+  }
+
+  public void setColor(Color c) {
+    northCanvas.setBackground(c);
+    southCanvas.setBackground(c);
+    eastCanvas.setBackground(c);
+    westCanvas.setBackground(c);
+  }
+}
diff --git a/src/net/infonode/docking/internal/ReadContext.java b/src/net/infonode/docking/internal/ReadContext.java
new file mode 100644
index 0000000..b96b796
--- /dev/null
+++ b/src/net/infonode/docking/internal/ReadContext.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ReadContext.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.internal;
+
+import net.infonode.docking.RootWindow;
+import net.infonode.docking.ViewSerializer;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class ReadContext {
+  private RootWindow rootWindow;
+  private int version;
+  private boolean propertyValuesAvailable;
+  private boolean readPropertiesEnabled;
+
+  public ReadContext(RootWindow rootWindow,
+                     int version,
+                     boolean propertyValuesAvailable,
+                     boolean readPropertiesEnabled) {
+    this.rootWindow = rootWindow;
+    this.version = version;
+    this.propertyValuesAvailable = propertyValuesAvailable;
+    this.readPropertiesEnabled = readPropertiesEnabled;
+  }
+
+  public RootWindow getRootWindow() {
+    return rootWindow;
+  }
+
+  public ViewSerializer getViewSerializer() {
+    return rootWindow.getViewSerializer();
+  }
+
+  public boolean isPropertyValuesAvailable() {
+    return propertyValuesAvailable;
+  }
+
+  public boolean getReadPropertiesEnabled() {
+    return readPropertiesEnabled;
+  }
+
+  /**
+   * @return returns the serialized version
+   */
+  public int getVersion() {
+    return version;
+  }
+}
diff --git a/src/net/infonode/docking/internal/ViewTitleBar.java b/src/net/infonode/docking/internal/ViewTitleBar.java
new file mode 100644
index 0000000..4b884ef
--- /dev/null
+++ b/src/net/infonode/docking/internal/ViewTitleBar.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ViewTitleBar.java,v 1.24 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.internal;
+
+import net.infonode.docking.View;
+import net.infonode.docking.internalutil.*;
+import net.infonode.docking.properties.ViewTitleBarProperties;
+import net.infonode.docking.properties.ViewTitleBarStateProperties;
+import net.infonode.gui.ContentTitleBar;
+import net.infonode.gui.DimensionProvider;
+import net.infonode.gui.hover.hoverable.HoverManager;
+import net.infonode.properties.gui.InternalPropertiesUtil;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.properties.propertymap.PropertyMapListener;
+import net.infonode.properties.propertymap.PropertyMapTreeListener;
+import net.infonode.properties.propertymap.PropertyMapWeakListenerManager;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author johan
+ */
+public class ViewTitleBar extends ContentTitleBar {
+  private static final ButtonInfo[] buttonInfos = {
+      new UndockButtonInfo(ViewTitleBarStateProperties.UNDOCK_BUTTON_PROPERTIES),
+      new DockButtonInfo(ViewTitleBarStateProperties.DOCK_BUTTON_PROPERTIES),
+      new MinimizeButtonInfo(ViewTitleBarStateProperties.MINIMIZE_BUTTON_PROPERTIES),
+      new MaximizeButtonInfo(ViewTitleBarStateProperties.MAXIMIZE_BUTTON_PROPERTIES),
+      new RestoreButtonInfo(ViewTitleBarStateProperties.RESTORE_BUTTON_PROPERTIES),
+      new CloseButtonInfo(ViewTitleBarStateProperties.CLOSE_BUTTON_PROPERTIES)};
+
+  private DimensionProvider minimumSizeProvider;
+  private net.infonode.docking.View view;
+  private AbstractButton[] buttons = new AbstractButton[buttonInfos.length];
+  private List customBarComponents;
+
+  private PropertyMapTreeListener propertiesListener = new PropertyMapTreeListener() {
+    public void propertyValuesChanged(Map changes) {
+      updateTitleBar(changes);
+    }
+  };
+
+  private PropertyMapListener buttonsListener = new PropertyMapListener() {
+    public void propertyValuesChanged(PropertyMap propertyMap, Map changes) {
+      updateViewButtons(null);
+    }
+  };
+
+  public ViewTitleBar(View view) {
+    super(view);
+    this.view = view;
+
+    PropertyMapWeakListenerManager.addWeakTreeListener(view.getViewProperties().getViewTitleBarProperties().getMap(),
+                                                       propertiesListener);
+    PropertyMapWeakListenerManager.addWeakListener(view.getWindowProperties().getMap(), buttonsListener);
+
+    updateTitleBar(null);
+    updateTitle();
+    updateViewButtons(null);
+
+    HoverManager.getInstance().addHoverable(this);
+  }
+
+  private void updateTitle() {
+    ViewTitleBarStateProperties titleProps = view.getViewProperties().getViewTitleBarProperties().getNormalProperties();
+    getLabel().setText(titleProps.getTitleVisible() ? titleProps.getTitle() : null);
+    getLabel().setIcon(titleProps.getIconVisible() ? titleProps.getIcon() : null);
+  }
+
+  private void updateTitleBar(Map changes) {
+    updateTitle();
+
+    JLabel label = getLabel();
+    ViewTitleBarProperties titleBarProperties = view.getViewProperties().getViewTitleBarProperties();
+    ShapedPanelProperties shapedProperties = titleBarProperties.getNormalProperties().getShapedPanelProperties();
+    ComponentProperties componentProperties = titleBarProperties.getNormalProperties().getComponentProperties();
+
+    updateViewButtons(changes);
+
+    if (changes == null) {
+      minimumSizeProvider = titleBarProperties.getMinimumSizeProvider();
+
+      componentProperties.applyTo(this);
+
+      for (int i = 0; i < buttons.length; i++)
+        if (buttons[i] != null) {
+          buttons[i].setForeground(componentProperties.getForegroundColor());
+          buttons[i].setBackground(
+              shapedProperties.getComponentPainter() != null ? null : componentProperties.getBackgroundColor());
+        }
+
+      {
+        label.setForeground(componentProperties.getForegroundColor());
+        label.setFont(componentProperties.getFont());
+        label.setIconTextGap(titleBarProperties.getNormalProperties().getIconTextGap());
+      }
+
+      {
+        InternalPropertiesUtil.applyTo(shapedProperties, this);
+      }
+
+      setLayoutDirection(titleBarProperties.getDirection());
+      setHoverListener(titleBarProperties.getHoverListener());
+      setLabelAlignment(titleBarProperties.getNormalProperties().getIconTextHorizontalAlignment());
+    }
+    else {
+      // View title bar
+      Map map = (Map) changes.get(titleBarProperties.getMap());
+      if (map != null) {
+        if (map.containsKey(ViewTitleBarProperties.MINIMUM_SIZE_PROVIDER)) {
+          minimumSizeProvider = titleBarProperties.getMinimumSizeProvider();
+          revalidate();
+        }
+
+        if (map.containsKey(ViewTitleBarProperties.DIRECTION))
+          setLayoutDirection(titleBarProperties.getDirection());
+
+        if (map.containsKey(ViewTitleBarProperties.HOVER_LISTENER))
+          setHoverListener(titleBarProperties.getHoverListener());
+
+      }
+
+      // State properties
+      map = (Map) changes.get(titleBarProperties.getNormalProperties().getMap());
+      if (map != null) {
+        if (map.containsKey(ViewTitleBarStateProperties.ICON_TEXT_GAP))
+          label.setIconTextGap(titleBarProperties.getNormalProperties().getIconTextGap());
+
+        if (map.containsKey(ViewTitleBarStateProperties.ICON_TEXT_HORIZONTAL_ALIGNMENT))
+          setLabelAlignment(titleBarProperties.getNormalProperties().getIconTextHorizontalAlignment());
+      }
+
+      // Component properties
+      map = (Map) changes.get(titleBarProperties.getNormalProperties().getComponentProperties().getMap());
+      if (map != null) {
+        componentProperties.applyTo(this);
+
+        label.setForeground(componentProperties.getForegroundColor());
+        label.setFont(componentProperties.getFont());
+
+        for (int i = 0; i < buttons.length; i++)
+          if (buttons[i] != null) {
+            buttons[i].setForeground(componentProperties.getForegroundColor());
+            buttons[i].setBackground(
+                shapedProperties.getComponentPainter() != null ? null : componentProperties.getBackgroundColor());
+          }
+      }
+
+      // Shaped panel properties
+      map = (Map) changes.get(titleBarProperties.getNormalProperties().getShapedPanelProperties().getMap());
+      if (map != null) {
+        InternalPropertiesUtil.applyTo(shapedProperties, this);
+      }
+    }
+  }
+
+  public void updateViewButtons(Map changes) {
+    InternalDockingUtil.updateButtons(buttonInfos,
+                                      buttons,
+                                      null,
+                                      view,
+                                      view.getViewProperties().getViewTitleBarProperties().getNormalProperties()
+                                          .getMap(),
+                                      changes);
+
+    if (shouldUpdateButtons())
+      updateCustomBarComponents(customBarComponents);
+  }
+
+  private boolean shouldUpdateButtons() {
+    JComponent[] titleComponents = getRightTitleComponents();
+
+    if (titleComponents == null || titleComponents.length < buttons.length)
+      return true;
+
+    for (int i = 0; i < buttons.length; i++)
+      if (titleComponents[titleComponents.length - i - 1] != buttons[i])
+        return true;
+
+    return false;
+  }
+
+  public void updateCustomBarComponents(List list) {
+    this.customBarComponents = list;
+
+    int size = list == null ? 0 : list.size();
+    JComponent[] components = new JComponent[size + buttons.length];
+    Insets[] insets = new Insets[components.length];
+
+    if (list != null) {
+      for (int i = 0; i < list.size(); i++) {
+        components[i] = (JComponent) list.get(i);
+        insets[i] = i == 0 ?
+                    new Insets(0, 0, 0, 0) :
+                    new Insets(0,
+                               view.getViewProperties().getViewTitleBarProperties().getNormalProperties()
+                                   .getButtonSpacing(),
+                               0,
+                               0);
+      }
+    }
+
+    for (int i = 0; i < buttons.length; i++) {
+      int k = size + i;
+      components[k] = buttons[i];
+      insets[k] = k == 0 ?
+                  new Insets(0, 0, 0, 0) :
+                  new Insets(0,
+                             view.getViewProperties().getViewTitleBarProperties().getNormalProperties()
+                                 .getButtonSpacing(),
+                             0,
+                             0);
+    }
+
+    setRightTitleComponents(components, insets);
+  }
+
+  public void dispose() {
+    HoverManager.getInstance().removeHoverable(this);
+  }
+
+  private int pressedCount = 0;
+  private boolean dragOutside = false;
+
+  protected void processMouseEvent(MouseEvent e) {
+    if (e.getID() == MouseEvent.MOUSE_PRESSED)
+      pressedCount++;
+
+    if (e.getID() == MouseEvent.MOUSE_RELEASED) {
+      pressedCount--;
+
+      if (pressedCount <= 0)
+        dragOutside = false;
+    }
+
+    super.processMouseEvent(e);
+  }
+
+  protected void processMouseMotionEvent(MouseEvent e) {
+    if (e.getID() == MouseEvent.MOUSE_DRAGGED && !dragOutside) {
+      dragOutside = !contains(e.getPoint());
+      if (!dragOutside)
+        return;
+    }
+
+    super.processMouseMotionEvent(e);
+  }
+
+  public Dimension getMinimumSize() {
+    if (minimumSizeProvider == null)
+      return super.getMinimumSize();
+
+    Dimension d = minimumSizeProvider.getDimension(this);
+    return d == null ? super.getMinimumSize() : d;
+  }
+
+  public Dimension getPreferredSize() {
+    Dimension d = minimumSizeProvider == null ? null : minimumSizeProvider.getDimension(this);
+
+    Dimension pSize = super.getPreferredSize();
+    if (d == null)
+      return pSize;
+
+    return new Dimension(Math.max(d.width, pSize.width), Math.max(d.height, pSize.height));
+  }
+}
diff --git a/src/net/infonode/docking/internal/WindowAncestors.java b/src/net/infonode/docking/internal/WindowAncestors.java
new file mode 100644
index 0000000..ca1603b
--- /dev/null
+++ b/src/net/infonode/docking/internal/WindowAncestors.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowAncestors.java,v 1.2 2005/12/04 13:35:12 jesper Exp $
+package net.infonode.docking.internal;
+
+import net.infonode.docking.DockingWindow;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ */
+public class WindowAncestors {
+  private DockingWindow[] ancestors;
+  private boolean minimized;
+  private boolean undocked;
+
+  public WindowAncestors(DockingWindow[] ancestors, boolean minimized, boolean undocked) {
+    this.ancestors = ancestors;
+    this.minimized = minimized;
+    this.undocked = undocked;
+  }
+
+  public DockingWindow[] getAncestors() {
+    return ancestors;
+  }
+
+  public boolean isMinimized() {
+    return minimized;
+  }
+
+  public boolean isUndocked() {
+    return undocked;
+  }
+}
diff --git a/src/net/infonode/docking/internal/WriteContext.java b/src/net/infonode/docking/internal/WriteContext.java
new file mode 100644
index 0000000..1b3d4ea
--- /dev/null
+++ b/src/net/infonode/docking/internal/WriteContext.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WriteContext.java,v 1.1 2004/12/14 16:39:59 jesper Exp $
+package net.infonode.docking.internal;
+
+import net.infonode.docking.ViewSerializer;
+
+/**
+ * Contains information used when writing a docking window state.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.1 $
+ */
+public class WriteContext {
+  private boolean writePropertiesEnabled;
+  private ViewSerializer viewSerializer;
+
+  /**
+   * Constructor.
+   *
+   * @param writePropertiesEnabled true if property values should be written
+   */
+  public WriteContext(boolean writePropertiesEnabled, ViewSerializer viewSerializer) {
+    this.writePropertiesEnabled = writePropertiesEnabled;
+    this.viewSerializer = viewSerializer;
+  }
+
+  /**
+   * Returns true if property values should be written.
+   *
+   * @return true if property values should be written
+   */
+  public boolean getWritePropertiesEnabled() {
+    return writePropertiesEnabled;
+  }
+
+  public ViewSerializer getViewSerializer() {
+    return viewSerializer;
+  }
+
+  public void setViewSerializer(ViewSerializer viewSerializer) {
+    this.viewSerializer = viewSerializer;
+  }
+}
diff --git a/src/net/infonode/docking/internalutil/AbstractButtonInfo.java b/src/net/infonode/docking/internalutil/AbstractButtonInfo.java
new file mode 100644
index 0000000..96048ad
--- /dev/null
+++ b/src/net/infonode/docking/internalutil/AbstractButtonInfo.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractButtonInfo.java,v 1.2 2004/09/23 15:32:13 jesper Exp $
+package net.infonode.docking.internalutil;
+
+import net.infonode.properties.propertymap.PropertyMapProperty;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ */
+abstract public class AbstractButtonInfo implements ButtonInfo {
+  private PropertyMapProperty property;
+
+  protected AbstractButtonInfo(PropertyMapProperty property) {
+    this.property = property;
+  }
+
+  public PropertyMapProperty getProperty() {
+    return property;
+  }
+}
diff --git a/src/net/infonode/docking/internalutil/ButtonInfo.java b/src/net/infonode/docking/internalutil/ButtonInfo.java
new file mode 100644
index 0000000..9f1ff72
--- /dev/null
+++ b/src/net/infonode/docking/internalutil/ButtonInfo.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ButtonInfo.java,v 1.5 2004/11/19 16:02:08 jesper Exp $
+package net.infonode.docking.internalutil;
+
+import net.infonode.properties.propertymap.PropertyMapProperty;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public interface ButtonInfo {
+  PropertyMapProperty getProperty();
+}
diff --git a/src/net/infonode/docking/internalutil/CloseButtonInfo.java b/src/net/infonode/docking/internalutil/CloseButtonInfo.java
new file mode 100644
index 0000000..8729d72
--- /dev/null
+++ b/src/net/infonode/docking/internalutil/CloseButtonInfo.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: CloseButtonInfo.java,v 1.7 2004/12/22 10:15:52 jesper Exp $
+package net.infonode.docking.internalutil;
+
+import net.infonode.properties.propertymap.PropertyMapProperty;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class CloseButtonInfo extends AbstractButtonInfo {
+  public CloseButtonInfo(PropertyMapProperty property) {
+    super(property);
+  }
+}
diff --git a/src/net/infonode/docking/internalutil/DockButtonInfo.java b/src/net/infonode/docking/internalutil/DockButtonInfo.java
new file mode 100644
index 0000000..3ed68d4
--- /dev/null
+++ b/src/net/infonode/docking/internalutil/DockButtonInfo.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockButtonInfo.java,v 1.2 2005/05/20 14:48:12 johan Exp $
+package net.infonode.docking.internalutil;
+
+import net.infonode.properties.propertymap.PropertyMapProperty;
+
+/**
+ * @author johan
+ */
+public class DockButtonInfo extends AbstractButtonInfo {
+  public DockButtonInfo(PropertyMapProperty property) {
+    super(property);
+  }
+}
diff --git a/src/net/infonode/docking/internalutil/DropAction.java b/src/net/infonode/docking/internalutil/DropAction.java
new file mode 100644
index 0000000..6d37dae
--- /dev/null
+++ b/src/net/infonode/docking/internalutil/DropAction.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DropAction.java,v 1.6 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.internalutil;
+
+import net.infonode.docking.DockingWindow;
+
+import java.awt.event.MouseEvent;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+abstract public class DropAction {
+  abstract public void execute(DockingWindow window, MouseEvent mouseEvent);
+
+  protected DropAction() {
+  }
+
+  public boolean showTitle() {
+    return true;
+  }
+
+  public void clear(DockingWindow window, DropAction newDropAction) {
+  }
+
+}
diff --git a/src/net/infonode/docking/internalutil/InternalDockingUtil.java b/src/net/infonode/docking/internalutil/InternalDockingUtil.java
new file mode 100644
index 0000000..d2df396
--- /dev/null
+++ b/src/net/infonode/docking/internalutil/InternalDockingUtil.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InternalDockingUtil.java,v 1.28 2009/02/05 15:57:55 jesper Exp $
+package net.infonode.docking.internalutil;
+
+import java.awt.Container;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Map;
+
+import javax.swing.AbstractButton;
+import javax.swing.JPopupMenu;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.RootWindow;
+import net.infonode.docking.TabWindow;
+import net.infonode.docking.View;
+import net.infonode.docking.action.DockingWindowAction;
+import net.infonode.docking.properties.WindowTabButtonProperties;
+import net.infonode.docking.util.DockingUtil;
+import net.infonode.docking.util.ViewMap;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.util.IntList;
+import net.infonode.util.Printer;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.28 $
+ */
+public class InternalDockingUtil {
+  private InternalDockingUtil() {
+  }
+
+  public static final int DEFAULT_BUTTON_ICON_SIZE = 10;
+
+  public static void getViews(DockingWindow window, ArrayList views) {
+    if (window == null)
+      return;
+    else if (window instanceof View)
+      views.add(window);
+    else {
+      for (int i = 0; i < window.getChildWindowCount(); i++)
+        getViews(window.getChildWindow(i), views);
+    }
+  }
+
+  public static IntList getWindowPath(DockingWindow window) {
+    return getWindowPath(window, IntList.EMPTY_LIST);
+  }
+
+  /**
+   * Returns the window located at <tt>windowPath</tt>.
+   *
+   * @param relativeToWindow the window the path is relative to
+   * @param windowPath       the window path
+   * @return the window located at <tt>windowPath</tt>
+   */
+  public static DockingWindow getWindow(DockingWindow relativeToWindow, IntList windowPath) {
+    return windowPath.isEmpty() ?
+                                 relativeToWindow :
+                                   windowPath.getValue() >= relativeToWindow.getChildWindowCount() ? null :
+                                     getWindow(relativeToWindow.getChildWindow(windowPath.getValue()), windowPath.getNext());
+  }
+
+  private static IntList getWindowPath(DockingWindow window, IntList tail) {
+    DockingWindow parent = window.getWindowParent();
+    return parent == null ? tail : getWindowPath(parent, new IntList(parent.getChildWindowIndex(window), tail));
+  }
+
+  public static void addDebugMenuItems(JPopupMenu menu, final DockingWindow window) {
+    menu.add("Dump Tree").addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        dump(window, new Printer());
+      }
+    });
+  }
+
+  public static void dump(DockingWindow window, Printer printer) {
+    DockingWindow parent = window.getWindowParent();
+
+    String clName = window.getClass().getName();
+
+    printer.println(clName.substring(clName.lastIndexOf('.') + 1) + ", " +
+        System.identityHashCode(window) + " (" +
+        (parent == null ? "null" : String.valueOf(System.identityHashCode(parent))) + "), '" +
+        window.getTitle() + "', " +
+        (window.isVisible() ? "visible" : "not visible") + ", " +
+        (window.isMaximized() ? "maximized" : "not maximized") + ", " +
+        (window.getChildWindowCount() > 0 ? ":" : ""));
+
+    if (window.getChildWindowCount() > 0) {
+      printer.beginSection();
+
+      for (int i = 0; i < window.getChildWindowCount(); i++) {
+        if (window.getChildWindow(i) == null)
+          printer.println("null");
+        else
+          dump(window.getChildWindow(i), printer);
+      }
+
+      printer.endSection();
+    }
+  }
+
+  public static RootWindow createInnerRootWindow(View[] views) {
+    RootWindow rootWindow = DockingUtil.createRootWindow(new ViewMap(views), true);
+    rootWindow.getRootWindowProperties().getWindowAreaProperties().setBackgroundColor(null);
+    rootWindow.getRootWindowProperties().getWindowAreaShapedPanelProperties().setComponentPainter(null);
+    rootWindow.getRootWindowProperties().getComponentProperties().setBackgroundColor(null);
+    rootWindow.getRootWindowProperties().getComponentProperties().setBorder(null);
+    //rootWindow.getRootWindowProperties().getWindowAreaProperties().setBorder(new LineBorder(Color.GRAY));
+    return rootWindow;
+  }
+
+  public static boolean updateButtons(ButtonInfo[] buttonInfos,
+                                      AbstractButton[] buttons,
+                                      Container container,
+                                      DockingWindow window,
+                                      PropertyMap map,
+                                      Map changes) {
+    //    DockingWindow window = w.getOptimizedWindow();
+    boolean updateContainer = false;
+
+    for (int i = 0; i < buttonInfos.length; i++) {
+      WindowTabButtonProperties p = new WindowTabButtonProperties(buttonInfos[i].getProperty().get(map));
+      DockingWindowAction action = p.getAction();
+      Map propertyChanges = changes == null ? null : (Map) changes.get(p.getMap());
+      //      boolean v = p.isVisible();
+      //      boolean b = action != null && action.isPerformable(window);
+      boolean visible = p.isVisible() && action != null && action.getAction(window).isEnabled();
+
+      if ((buttons[i] == null || (propertyChanges != null && propertyChanges.containsKey(
+          WindowTabButtonProperties.FACTORY))) &&
+          p.getFactory() != null &&
+          action != null) {
+        buttons[i] = p.getFactory().createButton(window);
+        buttons[i].setFocusable(false);
+        buttons[i].addActionListener(action.getAction(window).toSwingAction());
+        updateContainer = true;
+      }
+
+      if (buttons[i] != null) {
+        buttons[i].setToolTipText(p.getToolTipText());
+        buttons[i].setIcon(p.getIcon());
+        buttons[i].setVisible(visible);
+      }
+    }
+
+    if (updateContainer && container != null) {
+      container.removeAll();
+
+      for (int j = 0; j < buttonInfos.length; j++) {
+        if (buttons[j] != null)
+          container.add(buttons[j]);
+      }
+    }
+
+    return updateContainer;
+  }
+
+  public static void addToRootWindow(DockingWindow window, RootWindow rootWindow) {
+    if (rootWindow == null)
+      return;
+
+    DockingWindow w = rootWindow.getWindow();
+
+    if (w == null)
+      rootWindow.setWindow(window);
+    else if (w instanceof TabWindow)
+      ((TabWindow) w).addTab(window);
+    else
+      rootWindow.setWindow(new TabWindow(new DockingWindow[]{w, window}));
+  }
+}
diff --git a/src/net/infonode/docking/internalutil/MaximizeButtonInfo.java b/src/net/infonode/docking/internalutil/MaximizeButtonInfo.java
new file mode 100644
index 0000000..4f5cf5f
--- /dev/null
+++ b/src/net/infonode/docking/internalutil/MaximizeButtonInfo.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: MaximizeButtonInfo.java,v 1.6 2004/12/22 10:15:52 jesper Exp $
+package net.infonode.docking.internalutil;
+
+import net.infonode.properties.propertymap.PropertyMapProperty;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class MaximizeButtonInfo extends AbstractButtonInfo {
+  public MaximizeButtonInfo(PropertyMapProperty property) {
+    super(property);
+  }
+
+}
diff --git a/src/net/infonode/docking/internalutil/MinimizeButtonInfo.java b/src/net/infonode/docking/internalutil/MinimizeButtonInfo.java
new file mode 100644
index 0000000..1f2bc8a
--- /dev/null
+++ b/src/net/infonode/docking/internalutil/MinimizeButtonInfo.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: MinimizeButtonInfo.java,v 1.5 2004/12/22 10:15:52 jesper Exp $
+package net.infonode.docking.internalutil;
+
+import net.infonode.properties.propertymap.PropertyMapProperty;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class MinimizeButtonInfo extends AbstractButtonInfo {
+  public MinimizeButtonInfo(PropertyMapProperty property) {
+    super(property);
+  }
+
+}
diff --git a/src/net/infonode/docking/internalutil/RestoreButtonInfo.java b/src/net/infonode/docking/internalutil/RestoreButtonInfo.java
new file mode 100644
index 0000000..31ad165
--- /dev/null
+++ b/src/net/infonode/docking/internalutil/RestoreButtonInfo.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RestoreButtonInfo.java,v 1.7 2004/12/22 10:15:52 jesper Exp $
+package net.infonode.docking.internalutil;
+
+import net.infonode.properties.propertymap.PropertyMapProperty;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class RestoreButtonInfo extends AbstractButtonInfo {
+  public RestoreButtonInfo(PropertyMapProperty property) {
+    super(property);
+  }
+
+}
diff --git a/src/net/infonode/docking/internalutil/UndockButtonInfo.java b/src/net/infonode/docking/internalutil/UndockButtonInfo.java
new file mode 100644
index 0000000..c421132
--- /dev/null
+++ b/src/net/infonode/docking/internalutil/UndockButtonInfo.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: UndockButtonInfo.java,v 1.2 2005/05/20 14:48:12 johan Exp $
+package net.infonode.docking.internalutil;
+
+import net.infonode.properties.propertymap.PropertyMapProperty;
+
+/**
+ * @author johan
+ */
+public class UndockButtonInfo extends AbstractButtonInfo {
+  public UndockButtonInfo(PropertyMapProperty property) {
+    super(property);
+  }
+}
diff --git a/src/net/infonode/docking/location/AbstractWindowLocation.java b/src/net/infonode/docking/location/AbstractWindowLocation.java
new file mode 100644
index 0000000..198f498
--- /dev/null
+++ b/src/net/infonode/docking/location/AbstractWindowLocation.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractWindowLocation.java,v 1.15 2005/03/17 16:19:37 jesper Exp $
+package net.infonode.docking.location;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.RootWindow;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.util.IntList;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.WeakReference;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.15 $
+ */
+abstract public class AbstractWindowLocation implements WindowLocation {
+  private WindowLocation parentLocation;
+  private WeakReference window;
+
+  abstract protected boolean set(DockingWindow parent, DockingWindow child);
+
+  protected AbstractWindowLocation(DockingWindow window, WindowLocation parentLocation) {
+    this.window = new WeakReference(window);
+    this.parentLocation = parentLocation;
+  }
+
+  protected AbstractWindowLocation() {
+  }
+
+  public boolean set(DockingWindow window) {
+    DockingWindow w = getWindow();
+
+    if (w != null)
+      set(w, window);
+    else if (parentLocation != null)
+      parentLocation.set(window);
+
+    return true;
+  }
+
+  private DockingWindow getWindow() {
+    if (window == null)
+      return null;
+
+    DockingWindow w = (DockingWindow) window.get();
+    return w != null && w.getRootWindow() != null && !w.isMinimized() ? w : null;
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeBoolean(parentLocation != null);
+
+    if (parentLocation != null)
+      parentLocation.write(out);
+
+    DockingWindow w = getWindow();
+    out.writeBoolean(w != null);
+
+    if (w != null) {
+      InternalDockingUtil.getWindowPath(w).write(out);
+    }
+  }
+
+  protected void read(ObjectInputStream in, RootWindow rootWindow) throws IOException {
+    parentLocation = in.readBoolean() ? LocationDecoder.decode(in, rootWindow) : null;
+    window = in.readBoolean() ?
+             new WeakReference(InternalDockingUtil.getWindow(rootWindow, IntList.decode(in))) : null;
+  }
+
+}
diff --git a/src/net/infonode/docking/location/LocationDecoder.java b/src/net/infonode/docking/location/LocationDecoder.java
new file mode 100644
index 0000000..1338d1a
--- /dev/null
+++ b/src/net/infonode/docking/location/LocationDecoder.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: LocationDecoder.java,v 1.5 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.location;
+
+import net.infonode.docking.RootWindow;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class LocationDecoder {
+  public static final int NULL = 0;
+  public static final int ROOT = 1;
+  public static final int SPLIT = 2;
+  public static final int TAB = 3;
+
+  private LocationDecoder() {
+  }
+
+  public static WindowLocation decode(ObjectInputStream in, RootWindow rootWindow) throws IOException {
+    int type = in.readInt();
+
+    switch (type) {
+      case NULL:
+        return NullLocation.INSTANCE;
+
+      case ROOT:
+        return WindowRootLocation.decode(in, rootWindow);
+
+      case SPLIT:
+        return WindowSplitLocation.decode(in, rootWindow);
+
+      case TAB:
+        return WindowTabLocation.decode(in, rootWindow);
+
+      default:
+        throw new IOException("Invalid location type!");
+    }
+  }
+}
diff --git a/src/net/infonode/docking/location/NullLocation.java b/src/net/infonode/docking/location/NullLocation.java
new file mode 100644
index 0000000..18303f8
--- /dev/null
+++ b/src/net/infonode/docking/location/NullLocation.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: NullLocation.java,v 1.8 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.location;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.RootWindow;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class NullLocation implements WindowLocation {
+  public static final NullLocation INSTANCE = new NullLocation();
+
+  private NullLocation() {
+  }
+
+  public boolean set(DockingWindow window) {
+    RootWindow rootWindow = window.getRootWindow();
+
+    if (rootWindow == null)
+      return false;
+
+    InternalDockingUtil.addToRootWindow(window, rootWindow);
+    return true;
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(LocationDecoder.NULL);
+  }
+}
diff --git a/src/net/infonode/docking/location/WindowLocation.java b/src/net/infonode/docking/location/WindowLocation.java
new file mode 100644
index 0000000..de046ce
--- /dev/null
+++ b/src/net/infonode/docking/location/WindowLocation.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowLocation.java,v 1.6 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.location;
+
+import net.infonode.docking.DockingWindow;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public interface WindowLocation {
+  boolean set(DockingWindow window);
+
+  void write(ObjectOutputStream out) throws IOException;
+}
diff --git a/src/net/infonode/docking/location/WindowRootLocation.java b/src/net/infonode/docking/location/WindowRootLocation.java
new file mode 100644
index 0000000..5639199
--- /dev/null
+++ b/src/net/infonode/docking/location/WindowRootLocation.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowRootLocation.java,v 1.8 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.location;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.RootWindow;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class WindowRootLocation extends AbstractWindowLocation {
+  public WindowRootLocation(RootWindow rootWindow) {
+    super(rootWindow, null);
+  }
+
+  private WindowRootLocation() {
+  }
+
+  protected boolean set(DockingWindow parent, DockingWindow child) {
+    RootWindow rootWindow = (RootWindow) parent;
+    InternalDockingUtil.addToRootWindow(child, rootWindow);
+    return true;
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(LocationDecoder.ROOT);
+    super.write(out);
+  }
+
+  public static WindowRootLocation decode(ObjectInputStream in, RootWindow rootWindow) throws IOException {
+    WindowRootLocation location = new WindowRootLocation();
+    location.read(in, rootWindow);
+    return location;
+  }
+
+}
diff --git a/src/net/infonode/docking/location/WindowSplitLocation.java b/src/net/infonode/docking/location/WindowSplitLocation.java
new file mode 100644
index 0000000..fb29cde
--- /dev/null
+++ b/src/net/infonode/docking/location/WindowSplitLocation.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowSplitLocation.java,v 1.5 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.location;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.RootWindow;
+import net.infonode.util.Direction;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class WindowSplitLocation extends AbstractWindowLocation {
+  private Direction direction;
+  private float dividerLocation;
+
+  public WindowSplitLocation(DockingWindow splitWith,
+                             WindowLocation parentLocation,
+                             Direction direction,
+                             float dividerLocation) {
+    super(splitWith, parentLocation);
+    this.direction = direction;
+    this.dividerLocation = dividerLocation;
+  }
+
+  private WindowSplitLocation(Direction direction, float dividerLocation) {
+    this.direction = direction;
+    this.dividerLocation = dividerLocation;
+  }
+
+  public boolean set(DockingWindow parent, DockingWindow child) {
+    parent.split(child, direction, dividerLocation);
+    return true;
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(LocationDecoder.SPLIT);
+    direction.write(out);
+    out.writeFloat(dividerLocation);
+    super.write(out);
+  }
+
+  public static WindowSplitLocation decode(ObjectInputStream in, RootWindow rootWindow) throws IOException {
+    WindowSplitLocation location = new WindowSplitLocation(Direction.decode(in), in.readFloat());
+    location.read(in, rootWindow);
+    return location;
+  }
+}
diff --git a/src/net/infonode/docking/location/WindowTabLocation.java b/src/net/infonode/docking/location/WindowTabLocation.java
new file mode 100644
index 0000000..d138234
--- /dev/null
+++ b/src/net/infonode/docking/location/WindowTabLocation.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowTabLocation.java,v 1.5 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.location;
+
+import net.infonode.docking.AbstractTabWindow;
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.RootWindow;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class WindowTabLocation extends AbstractWindowLocation {
+  private int index;
+
+  public WindowTabLocation(AbstractTabWindow window, WindowLocation parentLocation, int index) {
+    super(window, parentLocation);
+    this.index = index;
+  }
+
+  private WindowTabLocation(int index) {
+    this.index = index;
+  }
+
+  public boolean set(DockingWindow parent, DockingWindow child) {
+    ((AbstractTabWindow) parent).addTab(child, index);
+    return true;
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(LocationDecoder.TAB);
+    out.writeInt(index);
+    super.write(out);
+  }
+
+  public static WindowTabLocation decode(ObjectInputStream in, RootWindow rootWindow) throws IOException {
+    WindowTabLocation location = new WindowTabLocation(in.readInt());
+    location.read(in, rootWindow);
+    return location;
+  }
+}
diff --git a/src/net/infonode/docking/model/AbstractTabWindowItem.java b/src/net/infonode/docking/model/AbstractTabWindowItem.java
new file mode 100644
index 0000000..98a681f
--- /dev/null
+++ b/src/net/infonode/docking/model/AbstractTabWindowItem.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractTabWindowItem.java,v 1.5 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.model;
+
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.WriteContext;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+abstract public class AbstractTabWindowItem extends WindowItem {
+  private WindowItem selectedItem;
+
+  protected AbstractTabWindowItem() {
+  }
+
+  protected AbstractTabWindowItem(WindowItem windowItem) {
+    super(windowItem);
+  }
+
+  public WindowItem getSelectedItem() {
+    return selectedItem;
+  }
+
+  public void setSelectedItem(WindowItem selectedItem) {
+    this.selectedItem = selectedItem;
+  }
+
+  public void writeSettings(ObjectOutputStream out, WriteContext context) throws IOException {
+    super.writeSettings(out, context);
+    out.writeInt(getWindowIndex(selectedItem));
+  }
+
+  public void readSettings(ObjectInputStream in, ReadContext context) throws IOException {
+    super.readSettings(in, context);
+
+    if (context.getVersion() >= 3) {
+      int selectedIndex = in.readInt();
+      selectedItem = selectedIndex == -1 ? null : getWindow(selectedIndex);
+    }
+  }
+
+}
diff --git a/src/net/infonode/docking/model/FloatingWindowItem.java b/src/net/infonode/docking/model/FloatingWindowItem.java
new file mode 100644
index 0000000..f9b90a7
--- /dev/null
+++ b/src/net/infonode/docking/model/FloatingWindowItem.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FloatingWindowItem.java,v 1.6 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.model;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.properties.FloatingWindowProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+
+import java.util.ArrayList;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class FloatingWindowItem extends WindowItem {
+  private FloatingWindowProperties properties = new FloatingWindowProperties();
+
+  protected DockingWindow createWindow(ViewReader viewReader, ArrayList childWindows) {
+    return null;
+  }
+
+  public WindowItem copy() {
+    return null;
+  }
+
+  public boolean isRestoreWindow() {
+    return false;
+  }
+
+  public FloatingWindowProperties getFloatingWindowProperties() {
+    return properties;
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return properties.getMap();
+  }
+
+}
diff --git a/src/net/infonode/docking/model/RootWindowItem.java b/src/net/infonode/docking/model/RootWindowItem.java
new file mode 100644
index 0000000..05079e4
--- /dev/null
+++ b/src/net/infonode/docking/model/RootWindowItem.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RootWindowItem.java,v 1.8 2005/03/11 13:16:49 jesper Exp $
+package net.infonode.docking.model;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.properties.RootWindowProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+
+import java.util.ArrayList;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class RootWindowItem extends WindowItem {
+  private RootWindowProperties rootWindowProperties = RootWindowProperties.createDefault();
+
+  public RootWindowItem() {
+  }
+
+  public RootWindowItem(RootWindowItem windowItem) {
+    super(windowItem);
+  }
+
+  protected PropertyMap createPropertyObject() {
+    return new RootWindowProperties().getMap();
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return rootWindowProperties.getMap();
+  }
+
+  public RootWindowProperties getRootWindowProperties() {
+    return rootWindowProperties;
+  }
+
+  public boolean isRestoreWindow() {
+    return true;
+  }
+
+  protected DockingWindow createWindow(ViewReader viewReader, ArrayList childWindows) {
+    return childWindows.size() == 0 ? null :
+           (DockingWindow) childWindows.get(0);
+  }
+
+  public RootWindowItem getRootItem() {
+    return this;
+  }
+
+  public WindowItem copy() {
+    return new RootWindowItem(this);
+  }
+
+}
diff --git a/src/net/infonode/docking/model/SplitWindowItem.java b/src/net/infonode/docking/model/SplitWindowItem.java
new file mode 100644
index 0000000..8e021d6
--- /dev/null
+++ b/src/net/infonode/docking/model/SplitWindowItem.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SplitWindowItem.java,v 1.12 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.model;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.properties.SplitWindowProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.12 $
+ */
+public class SplitWindowItem extends WindowItem {
+  public static final SplitWindowProperties emptyProperties = new SplitWindowProperties();
+
+  private boolean isHorizontal;
+  private float dividerLocation;
+  private SplitWindowProperties splitWindowProperties;
+  private SplitWindowProperties parentProperties = emptyProperties;
+
+  public SplitWindowItem() {
+    splitWindowProperties = new SplitWindowProperties(emptyProperties);
+  }
+
+  public SplitWindowItem(SplitWindowItem windowItem) {
+    super(windowItem);
+    splitWindowProperties = new SplitWindowProperties(windowItem.getSplitWindowProperties().getMap().copy(true, true));
+    splitWindowProperties.getMap().replaceSuperMap(windowItem.getParentSplitWindowProperties().getMap(),
+                                                   emptyProperties.getMap());
+  }
+
+  public SplitWindowItem(WindowItem leftWindow, WindowItem rightWindow, boolean horizontal, float dividerLocation) {
+    addWindow(leftWindow);
+    addWindow(rightWindow);
+    isHorizontal = horizontal;
+    this.dividerLocation = dividerLocation;
+  }
+
+  protected DockingWindow createWindow(ViewReader viewReader, ArrayList childWindows) {
+    return childWindows.size() == 0 ? null :
+           childWindows.size() == 1 ? (DockingWindow) childWindows.get(0) :
+           viewReader.createSplitWindow((DockingWindow) childWindows.get(0),
+                                        (DockingWindow) childWindows.get(1),
+                                        this);
+  }
+
+  public boolean isHorizontal() {
+    return isHorizontal;
+  }
+
+  public float getDividerLocation() {
+    return dividerLocation;
+  }
+
+  public void setHorizontal(boolean horizontal) {
+    isHorizontal = horizontal;
+  }
+
+  public void setDividerLocation(float dividerLocation) {
+    this.dividerLocation = dividerLocation;
+  }
+
+  public SplitWindowProperties getSplitWindowProperties() {
+    return splitWindowProperties;
+  }
+
+  public SplitWindowProperties getParentSplitWindowProperties() {
+    return parentProperties;
+  }
+
+  public void setParentSplitWindowProperties(SplitWindowProperties parentProperties) {
+    splitWindowProperties.getMap().replaceSuperMap(this.parentProperties.getMap(), parentProperties.getMap());
+    this.parentProperties = parentProperties;
+  }
+
+  public WindowItem copy() {
+    return new SplitWindowItem(this);
+  }
+
+  public void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+    out.writeInt(WindowItemDecoder.SPLIT);
+    super.write(out, context, viewWriter);
+  }
+
+  public void writeSettings(ObjectOutputStream out, WriteContext context) throws IOException {
+    out.writeBoolean(isHorizontal);
+    out.writeFloat(dividerLocation);
+    super.writeSettings(out, context);
+  }
+
+  public void readSettings(ObjectInputStream in, ReadContext context) throws IOException {
+    if (context.getVersion() >= 3) {
+      isHorizontal = in.readBoolean();
+      dividerLocation = in.readFloat();
+    }
+
+    super.readSettings(in, context);
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return getSplitWindowProperties().getMap();
+  }
+
+  public String toString() {
+    return "SplitWindow: " + super.toString();
+  }
+
+}
diff --git a/src/net/infonode/docking/model/TabWindowItem.java b/src/net/infonode/docking/model/TabWindowItem.java
new file mode 100644
index 0000000..3a27c23
--- /dev/null
+++ b/src/net/infonode/docking/model/TabWindowItem.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabWindowItem.java,v 1.13 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.model;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.properties.TabWindowProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.13 $
+ */
+public class TabWindowItem extends AbstractTabWindowItem {
+  public static final TabWindowProperties emptyProperties = new TabWindowProperties();
+
+  private TabWindowProperties tabWindowProperties;
+  private TabWindowProperties parentProperties = emptyProperties;
+
+  public TabWindowItem() {
+    tabWindowProperties = new TabWindowProperties(emptyProperties);
+  }
+
+  public TabWindowItem(TabWindowItem windowItem) {
+    super(windowItem);
+    tabWindowProperties = new TabWindowProperties(windowItem.getTabWindowProperties().getMap().copy(true, true));
+    tabWindowProperties.getMap().replaceSuperMap(windowItem.getParentTabWindowProperties().getMap(),
+                                                 emptyProperties.getMap());
+  }
+
+  protected DockingWindow createWindow(ViewReader viewReader, ArrayList childWindows) {
+    return childWindows.size() == 0 ? null :
+           viewReader.createTabWindow((DockingWindow[]) childWindows.toArray(new DockingWindow[childWindows.size()]),
+                                      this);
+  }
+
+  public TabWindowProperties getTabWindowProperties() {
+    return tabWindowProperties;
+  }
+
+  public void setTabWindowProperties(TabWindowProperties tabWindowProperties) {
+    this.tabWindowProperties = tabWindowProperties;
+  }
+
+  public TabWindowProperties getParentTabWindowProperties() {
+    return parentProperties;
+  }
+
+  public void setParentTabWindowProperties(TabWindowProperties parentProperties) {
+    tabWindowProperties.getMap().replaceSuperMap(this.parentProperties.getMap(), parentProperties.getMap());
+    this.parentProperties = parentProperties;
+  }
+
+  public WindowItem copy() {
+    return new TabWindowItem(this);
+  }
+
+  public void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+    out.writeInt(WindowItemDecoder.TAB);
+    super.write(out, context, viewWriter);
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return getTabWindowProperties().getMap();
+  }
+
+  public void clearWindows() {
+    // Do nothing
+  }
+
+  public String toString() {
+    return "TabWindow: " + super.toString();
+  }
+
+}
diff --git a/src/net/infonode/docking/model/ViewItem.java b/src/net/infonode/docking/model/ViewItem.java
new file mode 100644
index 0000000..a814b1e
--- /dev/null
+++ b/src/net/infonode/docking/model/ViewItem.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ViewItem.java,v 1.8 2005/06/19 20:56:31 jesper Exp $
+package net.infonode.docking.model;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.View;
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.properties.ViewProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class ViewItem extends WindowItem {
+  private ViewProperties viewProperties = new ViewProperties();
+
+  public ViewItem() {
+  }
+
+  public ViewItem(ViewItem viewItem) {
+    super(viewItem);
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return viewProperties.getMap();
+  }
+
+  protected DockingWindow createWindow(ViewReader viewReader, ArrayList childWindows) {
+    return null;
+  }
+
+  public ViewProperties getViewProperties() {
+    return viewProperties;
+  }
+
+  public void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+    out.writeInt(WindowItemDecoder.VIEW);
+    DockingWindow window = getConnectedWindow();
+    viewWriter.writeView((View) getConnectedWindow(), out, context);
+    out.writeBoolean(window != null && !window.isMinimized() && !window.isUndocked() && window.getRootWindow() != null);
+  }
+
+  public DockingWindow read(ObjectInputStream in, ReadContext context, ViewReader viewReader) throws IOException {
+    return in.readBoolean() ? getConnectedWindow() : null;
+  }
+
+  public WindowItem copy() {
+    return new ViewItem(this);
+  }
+
+}
diff --git a/src/net/infonode/docking/model/ViewReader.java b/src/net/infonode/docking/model/ViewReader.java
new file mode 100644
index 0000000..27a36d9
--- /dev/null
+++ b/src/net/infonode/docking/model/ViewReader.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ViewReader.java,v 1.3 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.model;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.SplitWindow;
+import net.infonode.docking.TabWindow;
+import net.infonode.docking.View;
+import net.infonode.docking.internal.ReadContext;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public interface ViewReader {
+  ViewItem readViewItem(ObjectInputStream in, ReadContext context) throws IOException;
+
+  View readView(ObjectInputStream in, ReadContext context) throws IOException;
+
+  TabWindow createTabWindow(DockingWindow[] childWindows, TabWindowItem windowItem);
+
+  SplitWindow createSplitWindow(DockingWindow leftWindow, DockingWindow rightWindow, SplitWindowItem windowItem);
+
+  WindowItem readWindowItem(ObjectInputStream in, ReadContext context) throws IOException;
+
+}
diff --git a/src/net/infonode/docking/model/ViewWriter.java b/src/net/infonode/docking/model/ViewWriter.java
new file mode 100644
index 0000000..d06d860
--- /dev/null
+++ b/src/net/infonode/docking/model/ViewWriter.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ViewWriter.java,v 1.3 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.model;
+
+import net.infonode.docking.View;
+import net.infonode.docking.internal.WriteContext;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public interface ViewWriter {
+  void writeWindowItem(WindowItem windowItem, ObjectOutputStream out, WriteContext context) throws IOException;
+
+  void writeView(View view, ObjectOutputStream out, WriteContext context) throws IOException;
+}
diff --git a/src/net/infonode/docking/model/WindowBarItem.java b/src/net/infonode/docking/model/WindowBarItem.java
new file mode 100644
index 0000000..cd9aae2
--- /dev/null
+++ b/src/net/infonode/docking/model/WindowBarItem.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowBarItem.java,v 1.12 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.model;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.properties.WindowBarProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+
+import java.util.ArrayList;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.12 $
+ */
+public class WindowBarItem extends AbstractTabWindowItem {
+  private WindowBarProperties windowBarProperties;
+
+  public WindowBarItem() {
+  }
+
+  public WindowBarItem(WindowBarItem windowBarItem) {
+    super(windowBarItem);
+  }
+
+  protected DockingWindow createWindow(ViewReader viewReader, ArrayList childWindows) {
+    return null;
+  }
+
+  public WindowItem copy() {
+    return new WindowBarItem(this);
+  }
+
+  public WindowBarProperties getWindowBarProperties() {
+    return windowBarProperties;
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return windowBarProperties.getMap();
+  }
+
+  public void setWindowBarProperties(WindowBarProperties windowBarProperties) {
+    this.windowBarProperties = windowBarProperties;
+  }
+
+  public boolean isRestoreWindow() {
+    return false;
+  }
+
+  public String toString() {
+    return "WindowBar:\n" + super.toString();
+  }
+
+}
diff --git a/src/net/infonode/docking/model/WindowItem.java b/src/net/infonode/docking/model/WindowItem.java
new file mode 100644
index 0000000..b948758
--- /dev/null
+++ b/src/net/infonode/docking/model/WindowItem.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowItem.java,v 1.20 2009/02/05 15:57:55 jesper Exp $
+package net.infonode.docking.model;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.internal.ReadContext;
+import net.infonode.docking.internal.WriteContext;
+import net.infonode.docking.properties.DockingWindowProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.properties.propertymap.PropertyMapUtil;
+import net.infonode.util.Direction;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.20 $
+ */
+abstract public class WindowItem {
+  public static final DockingWindowProperties emptyProperties = new DockingWindowProperties();
+
+  abstract protected DockingWindow createWindow(ViewReader viewReader, ArrayList childWindows);
+
+  abstract public WindowItem copy();
+
+  private WindowItem parent;
+  private WeakReference connectedWindow = new WeakReference(null);
+  private final ArrayList windows = new ArrayList();
+  private DockingWindowProperties dockingWindowProperties;
+  private DockingWindowProperties parentProperties = emptyProperties;
+  private Direction lastMinimizedDirection;
+
+  protected WindowItem() {
+    dockingWindowProperties = new DockingWindowProperties(emptyProperties);
+  }
+
+  protected WindowItem(WindowItem windowItem) {
+    dockingWindowProperties =
+      new DockingWindowProperties(windowItem.getDockingWindowProperties().getMap().copy(true, true));
+    dockingWindowProperties.getMap().replaceSuperMap(windowItem.getParentDockingWindowProperties().getMap(),
+        emptyProperties.getMap());
+    lastMinimizedDirection = windowItem.getLastMinimizedDirection();
+  }
+
+  public boolean isRestoreWindow() {
+    return parent != null && parent.isRestoreWindow();
+  }
+
+  public void addWindow(WindowItem item) {
+    if (item.parent != this)
+      addWindow(item, windows.size());
+  }
+
+  public void addWindow(WindowItem item, int index) {
+    index = index == -1 ? windows.size() : index;
+
+    if (item.parent == this) {
+      int currentIndex = windows.indexOf(item);
+
+      if (currentIndex != index) {
+        windows.remove(currentIndex);
+        windows.add(currentIndex < index ? index - 1 : index, item);
+      }
+    }
+    else {
+      item.setParent(this);
+      windows.add(index, item);
+    }
+  }
+
+  public void removeWindow(WindowItem item) {
+    if (windows.remove(item))
+      item.parent = null;
+  }
+
+  public void removeWindowRefs(DockingWindow window) {
+    if (connectedWindow.get() == window)
+      connectedWindow = new WeakReference(null);
+
+    for (int i = 0; i < getWindowCount(); i++)
+      getWindow(i).removeWindowRefs(window);
+  }
+
+  public void replaceWith(WindowItem item) {
+    if (item == this || parent == null)
+      return;
+
+    item.setParent(parent);
+    int index = parent.windows.indexOf(this);
+    parent.windows.set(index, item);
+    parent = null;
+  }
+
+  public int getWindowIndex(WindowItem item) {
+    return windows.indexOf(item);
+  }
+
+  private void setParent(WindowItem parent) {
+    if (this.parent == parent)
+      return;
+
+    if (this.parent != null)
+      this.parent.removeWindow(this);
+
+    this.parent = parent;
+  }
+
+  public final int getWindowCount() {
+    return windows.size();
+  }
+
+  public final WindowItem getWindow(int index) {
+    return (WindowItem) windows.get(index);
+  }
+
+  public WindowItem getParent() {
+    return parent;
+  }
+
+  public void setConnectedWindow(DockingWindow window) {
+    connectedWindow = new WeakReference(window);
+  }
+
+  public DockingWindow getConnectedWindow() {
+    return (DockingWindow) connectedWindow.get();
+  }
+
+  public RootWindowItem getRootItem() {
+    return parent == null ? null : parent.getRootItem();
+  }
+
+  public DockingWindow getVisibleDockingWindow() {
+    DockingWindow window = getConnectedWindow();
+
+    if (window != null && window.getRootWindow() != null && !window.isMinimized() && !window.isUndocked())
+      return window;
+
+    for (int i = 0; i < getWindowCount(); i++) {
+      WindowItem item = getWindow(i);
+      window = item.getVisibleDockingWindow();
+
+      if (window != null)
+        return window;
+    }
+
+    return null;
+  }
+
+  public DockingWindow getInsideDockingWindow() {
+    if (getParent() == null)
+      return null;
+
+    DockingWindow dockingWindow = getParent().getConnectedWindow();
+
+    if (dockingWindow != null)
+      return dockingWindow;
+
+    return getParent().getInsideDockingWindow();
+  }
+
+  public void removeAll() {
+    while (getWindowCount() > 0)
+      removeWindow(getWindow(0));
+  }
+
+  public boolean cleanUp() {
+    for (int i = getWindowCount() - 1; i >= 0; i--) {
+      if (getWindow(i).cleanUp())
+        windows.remove(i);
+    }
+
+    return getWindowCount() == 0 && getConnectedWindow() == null;
+  }
+
+  public DockingWindow getFirstChildWindow() {
+    for (int i = 0; i < getWindowCount(); i++) {
+      DockingWindow window = getWindow(i).getFirstWindow();
+
+      if (window != null)
+        return window;
+    }
+
+    return null;
+  }
+
+  public DockingWindow getFirstWindow() {
+    DockingWindow window = getConnectedWindow();
+    return window != null ? window : getFirstChildWindow();
+  }
+
+  public WindowItem getChildWindowContaining(WindowItem windowItem) {
+    while (windowItem.getParent() != this) {
+      windowItem = windowItem.getParent();
+
+      if (windowItem == null)
+        return null;
+    }
+
+    return windowItem;
+  }
+
+  public boolean hasAncestor(WindowItem ancestor) {
+    return this == ancestor || (parent != null && parent.hasAncestor(ancestor));
+  }
+
+  public WindowItem getTopItem() {
+    return parent == null ? this : parent.getTopItem();
+  }
+
+  public DockingWindowProperties getDockingWindowProperties() {
+    if (dockingWindowProperties == null) {
+      dockingWindowProperties = new DockingWindowProperties(emptyProperties);
+      parentProperties = emptyProperties;
+    }
+
+    return dockingWindowProperties;
+  }
+
+  public DockingWindowProperties getParentDockingWindowProperties() {
+    return parentProperties == null ? emptyProperties : parentProperties;
+  }
+
+  public void setParentDockingWindowProperties(DockingWindowProperties parentProperties) {
+    dockingWindowProperties.getMap().replaceSuperMap(this.parentProperties.getMap(),
+        parentProperties.getMap());
+    this.parentProperties = parentProperties;
+  }
+
+  public Direction getLastMinimizedDirection() {
+    return lastMinimizedDirection;
+  }
+
+  public void setLastMinimizedDirection(Direction lastMinimizedDirection) {
+    this.lastMinimizedDirection = lastMinimizedDirection;
+  }
+
+  public void writeSettings(ObjectOutputStream out, WriteContext context) throws IOException {
+    out.writeInt(getLastMinimizedDirection() == null ? -1 : getLastMinimizedDirection().getValue());
+
+    if (context.getWritePropertiesEnabled()) {
+      dockingWindowProperties.getMap().write(out, true);
+      getPropertyObject().write(out, true);
+    }
+  }
+
+  public void readSettings(ObjectInputStream in, ReadContext context) throws IOException {
+    if (context.getVersion() > 1) {
+      int dir = in.readInt();
+      setLastMinimizedDirection(dir == -1 ? null : Direction.getDirections()[dir]);
+    }
+
+    if (context.isPropertyValuesAvailable()) {
+      if (context.getReadPropertiesEnabled()) {
+        dockingWindowProperties.getMap().read(in);
+        getPropertyObject().read(in);
+      }
+      else {
+        PropertyMapUtil.skipMap(in);
+        PropertyMapUtil.skipMap(in);
+      }
+    }
+  }
+
+  public void write(ObjectOutputStream out, WriteContext context, ViewWriter viewWriter) throws IOException {
+    out.writeInt(getWindowCount());
+
+    for (int i = 0; i < getWindowCount(); i++)
+      getWindow(i).write(out, context, viewWriter);
+
+    DockingWindow window = getConnectedWindow();
+    writeSettings(out, context);
+    //    boolean b = window != null && !window.isMinimized() && !window.isUndocked() && window.getRootWindow() != null;
+    out.writeBoolean(window != null && !window.isMinimized() && !window.isUndocked() && window.getRootWindow() != null);
+  }
+
+  public DockingWindow read(ObjectInputStream in, ReadContext context, ViewReader viewReader) throws IOException {
+    ArrayList childWindows = readChildWindows(in, context, viewReader);
+    readSettings(in, context);
+    return in.readBoolean() ?
+                             createWindow(viewReader, childWindows) :
+                               childWindows.size() > 0 ? (DockingWindow) childWindows.get(0) : null;
+  }
+
+  public ArrayList readChildWindows(ObjectInputStream in, ReadContext context, ViewReader viewReader) throws
+  IOException {
+    int count = in.readInt();
+    removeAll();
+    ArrayList childWindows = new ArrayList();
+
+    for (int i = 0; i < count; i++) {
+      WindowItem childItem = WindowItemDecoder.decodeWindowItem(in, context, viewReader);
+      addWindow(childItem);
+      DockingWindow cw = childItem.read(in, context, viewReader);
+
+      if (cw != null)
+        childWindows.add(cw);
+    }
+
+    return childWindows;
+  }
+
+  protected PropertyMap getPropertyObject() {
+    return null;
+  }
+
+  public String toString() {
+    StringBuffer s = new StringBuffer();
+    DockingWindow dw = getConnectedWindow();
+    s.append(dw + ":\n");
+
+    for (int i = 0; i < windows.size(); i++)
+      s.append("  " + windows.get(i).toString());
+
+    return s.toString();
+  }
+
+  public void clearWindows() {
+    removeAll();
+  }
+
+}
diff --git a/src/net/infonode/docking/model/WindowItemDecoder.java b/src/net/infonode/docking/model/WindowItemDecoder.java
new file mode 100644
index 0000000..be435f5
--- /dev/null
+++ b/src/net/infonode/docking/model/WindowItemDecoder.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowItemDecoder.java,v 1.8 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.model;
+
+import net.infonode.docking.internal.ReadContext;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class WindowItemDecoder {
+  static final int SPLIT = 0;
+  static final int TAB = 1;
+  static final int VIEW = 2;
+
+  private WindowItemDecoder() {
+  }
+
+  static WindowItem decodeWindowItem(ObjectInputStream in, ReadContext context, ViewReader viewReader) throws
+                                                                                                       IOException {
+    int id = in.readInt();
+
+    switch (id) {
+      case SPLIT:
+        return new SplitWindowItem();
+
+      case TAB:
+        return new TabWindowItem();
+
+      case VIEW:
+        return viewReader.readViewItem(in, context);
+
+      default:
+        throw new IOException("Invalid window item id!");
+    }
+  }
+}
diff --git a/src/net/infonode/docking/mouse/DockingWindowActionMouseButtonListener.java b/src/net/infonode/docking/mouse/DockingWindowActionMouseButtonListener.java
new file mode 100644
index 0000000..0203577
--- /dev/null
+++ b/src/net/infonode/docking/mouse/DockingWindowActionMouseButtonListener.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowActionMouseButtonListener.java,v 1.9 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.mouse;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.action.CloseWithAbortWindowAction;
+import net.infonode.docking.action.DockingWindowAction;
+import net.infonode.gui.mouse.MouseButtonListener;
+
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.io.Serializable;
+
+/**
+ * A {@link MouseButtonListener} that performs a {@link DockingWindowAction}. The action is not performed
+ * if the mouse button event has been consumed.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ * @since IDW 1.3.0
+ */
+public class DockingWindowActionMouseButtonListener implements MouseButtonListener, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private static final int MODIFIER_MASK = InputEvent.ALT_DOWN_MASK |
+                                           InputEvent.SHIFT_DOWN_MASK |
+                                           InputEvent.CTRL_DOWN_MASK |
+                                           InputEvent.META_DOWN_MASK;
+
+  /**
+   * A listener that closes a window when its tab is clicked with the middle mouse button.
+   */
+  public static final MouseButtonListener MIDDLE_BUTTON_CLOSE_LISTENER =
+      new DockingWindowActionMouseButtonListener(MouseEvent.BUTTON2, CloseWithAbortWindowAction.INSTANCE);
+
+  private int eventId;
+  private int button;
+  private int keyMask;
+  private DockingWindowAction action;
+  private boolean consumeEvent;
+
+  /**
+   * Creates a listener which performs an action when a mouse button is clicked. The event is not consumed
+   * when the action is performed.
+   *
+   * @param button when this mouse button is clicked the action is performed , must be
+   *               {@link MouseEvent#BUTTON1}, {@link MouseEvent#BUTTON2} or {@link MouseEvent#BUTTON3}
+   * @param action the action to perform
+   */
+  public DockingWindowActionMouseButtonListener(int button, DockingWindowAction action) {
+    this(MouseEvent.MOUSE_CLICKED, button, action);
+  }
+
+  /**
+   * Creates a listener which performs an action when a mouse button is pressed, released or clicked.
+   * The event is not consumed when the action is performed.
+   *
+   * @param eventId the event type for which to perform the action, must be
+   *                {@link MouseEvent#MOUSE_PRESSED}, {@link MouseEvent#MOUSE_RELEASED} or
+   *                {@link MouseEvent#MOUSE_CLICKED}
+   * @param button  when this mouse button for which the action is performed , must be
+   *                {@link MouseEvent#BUTTON1}, {@link MouseEvent#BUTTON2} or {@link MouseEvent#BUTTON3}
+   * @param action  the action to perform
+   */
+  public DockingWindowActionMouseButtonListener(int eventId, int button, DockingWindowAction action) {
+    this(eventId, button, 0, action, false);
+  }
+
+  /**
+   * Creates a listener which performs an action when a mouse button is pressed, released or clicked, with
+   * an additional key mask.
+   *
+   * @param eventId      the event type for which to perform the action, must be
+   *                     {@link MouseEvent#MOUSE_PRESSED}, {@link MouseEvent#MOUSE_RELEASED} or
+   *                     {@link MouseEvent#MOUSE_CLICKED}
+   * @param button       when this mouse button for which the action is performed , must be
+   *                     {@link MouseEvent#BUTTON1}, {@link MouseEvent#BUTTON2} or {@link MouseEvent#BUTTON3}
+   * @param keyMask      the keys that must be pressed for the action to be performed, must be
+   *                     0 or an or'ed combination of the key down masks found in {@link InputEvent}.
+   * @param action       the action to perform
+   * @param consumeEvent if true the event is consumed when the action is performed
+   */
+  public DockingWindowActionMouseButtonListener(int eventId,
+                                                int button,
+                                                int keyMask,
+                                                DockingWindowAction action,
+                                                boolean consumeEvent) {
+    this.eventId = eventId;
+    this.button = button;
+    this.keyMask = keyMask;
+    this.action = action;
+    this.consumeEvent = consumeEvent;
+  }
+
+  public void mouseButtonEvent(MouseEvent event) {
+    if (event.isConsumed())
+      return;
+
+    int m = event.getModifiersEx() & MODIFIER_MASK;
+
+    if (event.getButton() == MouseEvent.BUTTON2)
+      m &= ~MouseEvent.ALT_DOWN_MASK;
+
+    if (event.getButton() == MouseEvent.BUTTON3)
+      m &= ~MouseEvent.META_DOWN_MASK;
+
+    if (event.getID() == eventId && event.getButton() == button && m == keyMask) {
+      DockingWindow window = (DockingWindow) event.getSource();
+      action.perform(window);
+
+      if (consumeEvent)
+        event.consume();
+    }
+  }
+
+}
diff --git a/src/net/infonode/docking/package.html b/src/net/infonode/docking/package.html
new file mode 100644
index 0000000..4bf7101
--- /dev/null
+++ b/src/net/infonode/docking/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Core classes for the docking windows framework.
+</body>
+</html>
diff --git a/src/net/infonode/docking/properties/DockingWindowDropFilterProperties.java b/src/net/infonode/docking/properties/DockingWindowDropFilterProperties.java
new file mode 100644
index 0000000..cae7386
--- /dev/null
+++ b/src/net/infonode/docking/properties/DockingWindowDropFilterProperties.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowDropFilterProperties.java,v 1.4 2007/01/28 21:25:10 jesper Exp $
+
+package net.infonode.docking.properties;
+
+import net.infonode.docking.drop.DropFilter;
+import net.infonode.docking.drop.DropFilterProperty;
+import net.infonode.properties.propertymap.*;
+
+/**
+ * Properties and property values for {@link net.infonode.docking.drop.DropFilter}s
+ * for all types of {@link net.infonode.docking.DockingWindow}s.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.4.0
+ */
+public class DockingWindowDropFilterProperties extends PropertyMapContainer {
+
+  /**
+   * Property group containing all docking window drop filter properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Docking Window Drop Filter Properties", "");
+
+  /**
+   * The {@link net.infonode.docking.drop.DropFilter} that is called when a
+   * split drop is in progress.
+   */
+  public static final DropFilterProperty SPLIT_DROP_FILTER = new DropFilterProperty(PROPERTIES,
+                                                                                    "Split Drop Filter",
+                                                                                    "The drop filter that is called when a split drop is in progress.",
+                                                                                    PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The {@link net.infonode.docking.drop.DropFilter} that is called when a
+   * child window will be asked for accept drop.
+   */
+  public static final DropFilterProperty CHILD_DROP_FILTER = new DropFilterProperty(PROPERTIES,
+                                                                                    "Child Drop Filter",
+                                                                                    "The drop filter that is called when a child window will be asked for accept drop.",
+                                                                                    PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The {@link net.infonode.docking.drop.DropFilter} that is called when an
+   * interior drop is in progress.
+   */
+  public static final DropFilterProperty INTERIOR_DROP_FILTER = new DropFilterProperty(PROPERTIES,
+                                                                                       "Interior Drop Filter",
+                                                                                       "The drop filter that is called when an interior drop is in progress.",
+                                                                                       PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The {@link net.infonode.docking.drop.DropFilter} that is called when an
+   * insert tab drop is in progress.
+   */
+  public static final DropFilterProperty INSERT_TAB_DROP_FILTER = new DropFilterProperty(PROPERTIES,
+                                                                                         "Insert Tab Drop Filter",
+                                                                                         "The drop filter that is called when an insert tab drop is in progress.",
+                                                                                         PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Creates an empty property object.
+   */
+  public DockingWindowDropFilterProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property object containing the map.
+   *
+   * @param map the property map
+   */
+  public DockingWindowDropFilterProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public DockingWindowDropFilterProperties(DockingWindowDropFilterProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public DockingWindowDropFilterProperties addSuperObject(DockingWindowDropFilterProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   */
+  public DockingWindowDropFilterProperties removeSuperObject(DockingWindowDropFilterProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Sets the split drop filter to be used when a split drop is in progress.
+   *
+   * @param filter the split drop filter
+   * @return this
+   */
+  public DockingWindowDropFilterProperties setSplitDropFilter(DropFilter filter) {
+    SPLIT_DROP_FILTER.set(getMap(), filter);
+    return this;
+  }
+
+  /**
+   * Returns the split drop filter that is used when a split drop is in
+   * progress.
+   *
+   * @return the split drop filter
+   */
+  public DropFilter getSplitDropFilter() {
+    return SPLIT_DROP_FILTER.get(getMap());
+  }
+
+  /**
+   * Sets the child drop filter to be used when a child window will be asked
+   * for accept drop.
+   *
+   * @param filter the child drop filter
+   * @return this
+   */
+  public DockingWindowDropFilterProperties setChildDropFilter(DropFilter filter) {
+    CHILD_DROP_FILTER.set(getMap(), filter);
+    return this;
+  }
+
+  /**
+   * Returns the child drop filter that is used when a child window will be asked
+   * for accept drop.
+   *
+   * @return the child drop filter
+   */
+  public DropFilter getChildDropFilter() {
+    return CHILD_DROP_FILTER.get(getMap());
+  }
+
+  /**
+   * Sets the interior drop filter to be used when an interior drop is in progress.
+   *
+   * @param filter the interior drop filter
+   * @return this
+   */
+  public DockingWindowDropFilterProperties setInteriorDropFilter(DropFilter filter) {
+    INTERIOR_DROP_FILTER.set(getMap(), filter);
+    return this;
+  }
+
+  /**
+   * Returns the interior drop filter that is used when an interior drop is in progress.
+   *
+   * @return the interior drop filter
+   */
+  public DropFilter getInteriorDropFilter() {
+    return INTERIOR_DROP_FILTER.get(getMap());
+  }
+
+
+  /**
+   * Sets the insert tab drop filter to be used when an insert tab drop is in progress.
+   *
+   * @param filter the insert tab drop filter
+   * @return this
+   */
+  public DockingWindowDropFilterProperties setInsertTabDropFilter(DropFilter filter) {
+    INSERT_TAB_DROP_FILTER.set(getMap(), filter);
+    return this;
+  }
+
+  /**
+   * Returns the insert tab drop filter that is used when an insert tab drop is in progress.
+   *
+   * @return the child drop filter
+   */
+  public DropFilter getInsertTabDropFilter() {
+    return INSERT_TAB_DROP_FILTER.get(getMap());
+  }
+}
diff --git a/src/net/infonode/docking/properties/DockingWindowProperties.java b/src/net/infonode/docking/properties/DockingWindowProperties.java
new file mode 100644
index 0000000..d50be4c
--- /dev/null
+++ b/src/net/infonode/docking/properties/DockingWindowProperties.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowProperties.java,v 1.25 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.docking.title.DockingWindowTitleProvider;
+import net.infonode.docking.title.DockingWindowTitleProviderProperty;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+
+/**
+ * Properties and property values common for all docking windows.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.25 $
+ */
+public class DockingWindowProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all docking window properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Docking Window Properties", "");
+
+  /**
+   * Property values for the window tab when the window is located in a TabWindow or a WindowBar.
+   */
+  public static final PropertyMapProperty TAB_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Tab Properties",
+                              "Property values for the window tab when the window is located in a TabWindow or a WindowBar.",
+                              WindowTabProperties.PROPERTIES);
+
+  /**
+   * Property values for drop filters.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final PropertyMapProperty DROP_FILTER_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Drop Filter Properties",
+                              "Property values for drop filters.",
+                              DockingWindowDropFilterProperties.PROPERTIES);
+
+  /**
+   * Enables/disables window drag by the user.
+   *
+   * @since IDW 1.2.0
+   */
+  public static final BooleanProperty DRAG_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Drag Enabled",
+                          "Enables/disables window drag by the user.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Enables/disables undock to a floating window.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final BooleanProperty UNDOCK_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Undock Enabled",
+                          "Enables/disables if a window can be undocked to a floating window.",
+                          PropertyMapValueHandler.INSTANCE);
+  /**
+   * <p>
+   * Enables/disables undock when dropped outside root window.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> This property will only have effect if window drag is enabled and undocking is enabled.
+   * </p>
+   *
+   * @since IDW 1.4.0
+   */
+  public static final BooleanProperty UNDOCK_ON_DROP =
+      new BooleanProperty(PROPERTIES,
+                          "Undock when Dropped",
+                          "Enables/disables window undock to floating window when a drag and drop is performed outside the root window.",
+                          PropertyMapValueHandler.INSTANCE);
+
+
+  /**
+   * Enables/disables undock to a floating window.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final BooleanProperty DOCK_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Dock Enabled",
+                          "Enables/disables if a window can be docked to the root window from a floating window.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Enables/disables window minimize by the user.
+   *
+   * @since IDW 1.2.0
+   */
+  public static final BooleanProperty MINIMIZE_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Minimize Enabled",
+                          "Enables/disables window minimize by the user.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Enables/disables window close by the user.
+   *
+   * @since IDW 1.2.0
+   */
+  public static final BooleanProperty CLOSE_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Close Enabled",
+                          "Enables/disables window close by the user.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Enables/disables window restore by the user.
+   *
+   * @since IDW 1.2.0
+   */
+  public static final BooleanProperty RESTORE_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Restore Enabled",
+                          "Enables/disables window restore by the user.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Enables/disables window maximize by the user.
+   *
+   * @since IDW 1.2.0
+   */
+  public static final BooleanProperty MAXIMIZE_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Maximize Enabled",
+                          "Enables/disables window maximize by the user.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Provides a title for a window.
+   *
+   * @since IDW 1.3.0
+   */
+  public static final DockingWindowTitleProviderProperty TITLE_PROVIDER =
+      new DockingWindowTitleProviderProperty(PROPERTIES,
+                                             "Title Provider",
+                                             "Provides a title for a window.",
+                                             PropertyMapValueHandler.INSTANCE);
+
+
+  /**
+   * Creates an empty property object.
+   */
+  public DockingWindowProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property map containing the map.
+   *
+   * @param map the property map
+   */
+  public DockingWindowProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public DockingWindowProperties(DockingWindowProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public DockingWindowProperties addSuperObject(DockingWindowProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   * @since IDW 1.1.0
+   * @deprecated Use {@link #removeSuperObject(DockingWindowProperties)} instead.
+   */
+  public DockingWindowProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public DockingWindowProperties removeSuperObject(DockingWindowProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Returns the property values for the window tab when the window is located in a TabWindow or a WindowBar.
+   *
+   * @return the property values for the window tab when the window is located in a TabWindow or a WindowBar
+   */
+  public WindowTabProperties getTabProperties() {
+    return new WindowTabProperties(TAB_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the property values for drop filters.
+   *
+   * @return the property values for drop filters
+   * @since IDW 1.4.0
+   */
+  public DockingWindowDropFilterProperties getDropFilterProperties() {
+    return new DockingWindowDropFilterProperties(DROP_FILTER_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns true if the window drag by the user is enabled.
+   *
+   * @return true if the window drag is enabled
+   * @since IDW 1.2.0
+   */
+  public boolean getDragEnabled() {
+    return DRAG_ENABLED.get(getMap());
+  }
+
+  /**
+   * Enables/disables window drag by the user.
+   *
+   * @param enabled if true, drag is enabled, otherwise it's disabled
+   * @return this
+   * @since IDW 1.2.0
+   */
+  public DockingWindowProperties setDragEnabled(boolean enabled) {
+    DRAG_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Returns true if the window can be undocked to a floating window.
+   *
+   * @return true if undocking is enabled
+   * @since IDW 1.4.0
+   */
+  public boolean getUndockEnabled() {
+    return UNDOCK_ENABLED.get(getMap());
+  }
+
+  /**
+   * Enables/disables undock to floating window.
+   *
+   * @param enabled if true, a window can be undocked to a floating window,
+   *                otherwise it's disabled
+   * @return this
+   * @since IDW 1.4.0
+   */
+  public DockingWindowProperties setUndockEnabled(boolean enabled) {
+    UNDOCK_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Returns true if the window drag by the user and is dropped outside the root window should undock to a floating
+   * window.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> This property will only have effect if drag is enabled.
+   * </p>
+   *
+   * @return true if the dropped window should undock to a floating window
+   * @since IDW 1.4.0
+   */
+  public boolean getUndockOnDropEnabled() {
+    return UNDOCK_ON_DROP.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Enables/disables if the window drag by the user and is dropped outside the root window should undock to a floating
+   * window or not.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> This property will only have effect if drag is enabled.
+   * </p>
+   *
+   * @param enabled if true, drop to floating window is enabled, otherwise it's disabled
+   * @return this
+   * @since IDW 1.4.0
+   */
+  public DockingWindowProperties setUndockOnDropEnabled(boolean enabled) {
+    UNDOCK_ON_DROP.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Returns true if the window can be docked to the root window from a floating window.
+   *
+   * @return true if docking is enabled
+   * @since IDW 1.4.0
+   */
+  public boolean getDockEnabled() {
+    return DOCK_ENABLED.get(getMap());
+  }
+
+  /**
+   * Enables/disables dock to the root window from a floating window.
+   *
+   * @param enabled if true, a window can be docked to the root window from a floating window,
+   *                otherwise it's disabled
+   * @return this
+   * @since IDW 1.4.0
+   */
+  public DockingWindowProperties setDockEnabled(boolean enabled) {
+    DOCK_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Returns true if the window minimize by the user is enabled.
+   *
+   * @return true if the window minimize is enabled
+   * @since IDW 1.2.0
+   */
+  public boolean getMinimizeEnabled() {
+    return MINIMIZE_ENABLED.get(getMap());
+  }
+
+  /**
+   * Enables/disables window minimize by the user.
+   *
+   * @param enabled if true, minimize is enabled, otherwise it's disabled
+   * @return this
+   * @since IDW 1.2.0
+   */
+  public DockingWindowProperties setMinimizeEnabled(boolean enabled) {
+    MINIMIZE_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Returns true if the window maximize by the user is enabled.
+   *
+   * @return true if the window maximize is enabled
+   * @since IDW 1.2.0
+   */
+  public boolean getMaximizeEnabled() {
+    return MAXIMIZE_ENABLED.get(getMap());
+  }
+
+  /**
+   * Enables/disables window maximize by the user.
+   *
+   * @param enabled if true, maximize is enabled, otherwise it's disabled
+   * @return this
+   * @since IDW 1.2.0
+   */
+  public DockingWindowProperties setMaximizeEnabled(boolean enabled) {
+    MAXIMIZE_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Returns true if the window close by the user is enabled.
+   *
+   * @return true if the window close is enabled
+   * @since IDW 1.2.0
+   */
+  public boolean getCloseEnabled() {
+    return CLOSE_ENABLED.get(getMap());
+  }
+
+  /**
+   * Enables/disables window close by the user.
+   *
+   * @param enabled if true, close is enabled, otherwise it's disabled
+   * @return this
+   * @since IDW 1.2.0
+   */
+  public DockingWindowProperties setCloseEnabled(boolean enabled) {
+    CLOSE_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Returns true if the window restore by the user is enabled.
+   *
+   * @return true if the window restore is enabled
+   * @since IDW 1.2.0
+   */
+  public boolean getRestoreEnabled() {
+    return RESTORE_ENABLED.get(getMap());
+  }
+
+  /**
+   * Enables/disables window restore by the user.
+   *
+   * @param enabled if true, restore is enabled, otherwise it's disabled
+   * @return this
+   * @since IDW 1.2.0
+   */
+  public DockingWindowProperties setRestoreEnabled(boolean enabled) {
+    RESTORE_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Returns the title provider for the window.
+   *
+   * @return the title provider for the window
+   * @since IDW 1.3.0
+   */
+  public DockingWindowTitleProvider getTitleProvider() {
+    return TITLE_PROVIDER.get(getMap());
+  }
+
+  /**
+   * Sets the title provider for the window.
+   *
+   * @param titleProvider the title provider for the window
+   * @since IDW 1.3.0
+   */
+  public DockingWindowProperties setTitleProvider(DockingWindowTitleProvider titleProvider) {
+    TITLE_PROVIDER.set(getMap(), titleProvider);
+    return this;
+  }
+
+}
diff --git a/src/net/infonode/docking/properties/FloatingWindowProperties.java b/src/net/infonode/docking/properties/FloatingWindowProperties.java
new file mode 100644
index 0000000..ee06dbc
--- /dev/null
+++ b/src/net/infonode/docking/properties/FloatingWindowProperties.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FloatingWindowProperties.java,v 1.6 2007/01/07 19:11:20 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+
+/**
+ * Properties and property values for floating windows.
+ *
+ * @author $Author: jesper $
+ * @since IDW 1.4.0
+ */
+public class FloatingWindowProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all floating window properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Floating Window Properties", "");
+
+  /**
+   * Properties for the component
+   *
+   * @see #getComponentProperties
+   */
+  public static final PropertyMapProperty COMPONENT_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                         "Component Properties",
+                                                                                         "Component properties for floating window.",
+                                                                                         ComponentProperties.PROPERTIES);
+
+  /**
+   * Properties for the shaped panel
+   *
+   * @see #getShapedPanelProperties
+   */
+  public static final PropertyMapProperty SHAPED_PANEL_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                            "Shaped Panel Properties",
+                                                                                            "Properties for floating window internal shape.",
+                                                                                            ShapedPanelProperties.PROPERTIES);
+
+  /**
+   * Auto close enabled
+   */
+  public static final BooleanProperty AUTO_CLOSE_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Auto Close Enabled",
+                          "Enables/disables if the floating window should be automatically closed when it doesn't contain any child window.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * If true the floating window will be created as a JFrame, otherwise a JDialog will be created.
+   * Note that the value of this property only takes effect when the FloatingWindow is created, it doesn't affect
+   * existing FloatingWindows (but this might change in future versions).
+   *
+   * @since IDW 1.5.0
+   */
+  public static final BooleanProperty USE_FRAME =
+      new BooleanProperty(PROPERTIES,
+                          "Use Frame",
+                          "If true the floating window will be created as a JFrame, otherwise a JDialog will be created.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Creates an empty property object.
+   */
+  public FloatingWindowProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property map containing the map.
+   *
+   * @param map the property map
+   */
+  public FloatingWindowProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public FloatingWindowProperties(FloatingWindowProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public FloatingWindowProperties addSuperObject(FloatingWindowProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   */
+  public FloatingWindowProperties removeSuperObject(FloatingWindowProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Gets the component properties
+   *
+   * @return component properties
+   */
+  public ComponentProperties getComponentProperties() {
+    return new ComponentProperties(COMPONENT_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the shaped panel properties
+   *
+   * @return shaped panel properties
+   */
+  public ShapedPanelProperties getShapedPanelProperties() {
+    return new ShapedPanelProperties(SHAPED_PANEL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns true if the floating window should be automatically closed when it doesn't
+   * contain any child window.
+   *
+   * @return true if auto close is enabled, otherwise false
+   */
+  public boolean getAutoCloseEnabled() {
+    return AUTO_CLOSE_ENABLED.get(getMap());
+  }
+
+  /**
+   * Enables/disables if the floating window should be automatically closed when it doesn't contain
+   * any child window.
+   *
+   * @param enabled true for auto close, otherwise disabled
+   * @return this
+   */
+  public FloatingWindowProperties setAutoCloseEnabled(boolean enabled) {
+    AUTO_CLOSE_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Returns true if the floating window should be created as a JFrame, otherwise a JDialog is used.
+   *
+   * @return true if a JFrame should be used
+   * @since IDW 1.5.0
+   */
+  public boolean getUseFrame() {
+    return USE_FRAME.get(getMap());
+  }
+
+  /**
+   * Set to true if the floating window should be created as a JFrame, otherwise a JDialog is used.
+   * Note that the value of this property only takes effect when the FloatingWindow is created, it doesn't affect
+   * existing FloatingWindows (but this might change in future versions).
+   *
+   * @param enabled true if a JFrame should be used
+   * @return this
+   * @since IDW 1.5.0
+   */
+  public FloatingWindowProperties setUseFrame(boolean enabled) {
+    USE_FRAME.set(getMap(), enabled);
+    return this;
+  }
+}
diff --git a/src/net/infonode/docking/properties/RootWindowProperties.java b/src/net/infonode/docking/properties/RootWindowProperties.java
new file mode 100644
index 0000000..a84e71f
--- /dev/null
+++ b/src/net/infonode/docking/properties/RootWindowProperties.java
@@ -0,0 +1,942 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RootWindowProperties.java,v 1.96 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.docking.DefaultButtonFactories;
+import net.infonode.docking.action.*;
+import net.infonode.docking.drop.AcceptAllDropFilter;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.gui.DynamicUIManager;
+import net.infonode.gui.DynamicUIManagerListener;
+import net.infonode.gui.UIManagerUtil;
+import net.infonode.gui.colorprovider.FixedColorProvider;
+import net.infonode.gui.componentpainter.ComponentPainter;
+import net.infonode.gui.componentpainter.GradientComponentPainter;
+import net.infonode.gui.componentpainter.SolidColorComponentPainter;
+import net.infonode.properties.base.Property;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+import net.infonode.properties.types.IntegerProperty;
+import net.infonode.tabbedpanel.*;
+import net.infonode.tabbedpanel.border.TabAreaLineBorder;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+import net.infonode.tabbedpanel.titledtab.TitledTabSizePolicy;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import javax.swing.border.LineBorder;
+import java.awt.*;
+import java.util.Map;
+
+/**
+ * Properties and property values for a root window.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.96 $
+ */
+public class RootWindowProperties extends PropertyMapContainer {
+  /**
+   * The size of the default window tab button icons.
+   */
+  public static final int DEFAULT_WINDOW_TAB_BUTTON_ICON_SIZE = InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE;
+
+  /**
+   * Property group containing all root window properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Root Window Properties", "");
+
+  /**
+   * The root window component property values.
+   */
+  public static final PropertyMapProperty COMPONENT_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Component Properties",
+                              "The root window component property values.",
+                              ComponentProperties.PROPERTIES);
+
+  /**
+   * The root window shaped panel property values.
+   */
+  public static final PropertyMapProperty SHAPED_PANEL_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Shaped Panel Properties",
+                              "The root window shaped panel property values.",
+                              ShapedPanelProperties.PROPERTIES);
+
+  /**
+   * The window area component property values. The window area is the area inside the WindowBars.
+   */
+  public static final PropertyMapProperty WINDOW_AREA_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Window Area Properties",
+                              "The window area component property values. The window area is the area inside the WindowBars.",
+                              ComponentProperties.PROPERTIES);
+
+  /**
+   * The window area shaped panel property values. The window area is the area inside the WindowBars.
+   */
+  public static final PropertyMapProperty WINDOW_AREA_SHAPED_PANEL_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Window Area Shaped Panel Properties",
+                              "The window area shaped panel property values. The window area is the area inside the WindowBars.",
+                              ShapedPanelProperties.PROPERTIES);
+
+  /**
+   * Shaped panel properties for the drag rectangle. Setting a painter disables the default drag rectangle.
+   *
+   * @since IDW 1.2.0
+   */
+  public static final PropertyMapProperty DRAG_RECTANGLE_SHAPED_PANEL_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Drag Rectangle Shaped Panel Properties",
+                              "Shaped panel properties for the drag rectangle. Setting a painter disables the default drag rectangle.",
+                              ShapedPanelProperties.PROPERTIES);
+
+  /**
+   * The width of the drag rectangle border.
+   */
+  public static final IntegerProperty DRAG_RECTANGLE_BORDER_WIDTH =
+      IntegerProperty.createPositive(PROPERTIES,
+                                     "Drag Rectangle Border Width",
+                                     "The width of the drag rectangle border. The drag rectangle will only " +
+                                     "be painted if the painter of the '" +
+                                     DRAG_RECTANGLE_SHAPED_PANEL_PROPERTIES.getName() +
+                                     "' property is not set.",
+                                     2,
+                                     PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The window drag label property values.
+   */
+  public static final PropertyMapProperty DRAG_LABEL_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Drag Label Properties",
+                              "The window drag label property values.",
+                              ComponentProperties.PROPERTIES);
+
+  /**
+   * Default property values for DockingWindows inside this root window.
+   */
+  public static final PropertyMapProperty DOCKING_WINDOW_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Docking Window Properties",
+                              "Default property values for DockingWindows inside this RootWindow.",
+                              DockingWindowProperties.PROPERTIES);
+
+  /**
+   * Default property values for tab windows inside this root window.
+   */
+  public static final PropertyMapProperty TAB_WINDOW_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Tab Window Properties",
+                              "Default property values for TabWindows inside this RootWindow.",
+                              TabWindowProperties.PROPERTIES);
+
+  /**
+   * Default property values for split windows inside this root window.
+   */
+  public static final PropertyMapProperty SPLIT_WINDOW_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Split Window Properties",
+                              "Default property values for SplitWindows inside this RootWindow.",
+                              SplitWindowProperties.PROPERTIES);
+
+  /**
+   * Default property values for floating windows inside this root window.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final PropertyMapProperty FLOATING_WINDOW_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Floating Window Properties",
+                              "Default property values for FloatingWindows inside this RootWindow.",
+                              FloatingWindowProperties.PROPERTIES);
+
+  /**
+   * Default property values for views inside this root window.
+   */
+  public static final PropertyMapProperty VIEW_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "View Properties",
+                              "Default property values for Views inside this RootWindow.",
+                              ViewProperties.PROPERTIES);
+
+  /**
+   * Double clicking on a minimized window in a window bar restores it.
+   */
+  public static final BooleanProperty DOUBLE_CLICK_RESTORES_WINDOW =
+      new BooleanProperty(PROPERTIES,
+                          "Double Click Restores Window",
+                          "Double clicking on a minimized window in a window bar restores it.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * If true, makes it possible for the user to create tab windows inside other tab windows when dragging windows.
+   * If false, only one level of tab windows is allowed.
+   * Changing the value of this property does not alter the window tree.
+   */
+  public static final BooleanProperty RECURSIVE_TABS_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Recursive Tabs Enabled",
+                          "If true, makes it possible for the user to create tab windows inside other tab windows by " +
+                          "dragging windows. If false, only one level of tab windows is allowed. Changing the value of " +
+                          "this property does not alter the window tree.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Inside this distance from the window edge a mouse drag will trigger a window split.
+   */
+  public static final IntegerProperty EDGE_SPLIT_DISTANCE =
+      IntegerProperty.createPositive(PROPERTIES,
+                                     "Edge Split Distance",
+                                     "Inside this distance from the window edge a mouse drag will trigger a window split.",
+                                     3,
+                                     PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Key code for the key that aborts a drag.
+   */
+  public static final IntegerProperty ABORT_DRAG_KEY =
+      IntegerProperty.createPositive(PROPERTIES,
+                                     "Abort Drag Key Code",
+                                     "Key code for the key that aborts a drag.",
+                                     3,
+                                     PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The default window bar property values.
+   *
+   * @since IDW 1.1.0
+   */
+  public static final PropertyMapProperty WINDOW_BAR_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Window Bar Properties",
+                              "Default property values for WindowBars inside this RootWindow.",
+                              WindowBarProperties.PROPERTIES);
+
+  private static RootWindowProperties DEFAULT_VALUES;
+
+  private static void setTabProperties() {
+    WindowTabProperties tabProperties = DEFAULT_VALUES.getTabWindowProperties().getTabProperties();
+
+    PropertyMapProperty[] buttonProperties = {WindowTabStateProperties.CLOSE_BUTTON_PROPERTIES,
+                                              WindowTabStateProperties.MINIMIZE_BUTTON_PROPERTIES,
+                                              WindowTabStateProperties.RESTORE_BUTTON_PROPERTIES,
+                                              WindowTabStateProperties.UNDOCK_BUTTON_PROPERTIES,
+                                              WindowTabStateProperties.DOCK_BUTTON_PROPERTIES};
+
+    for (int i = 0; i < buttonProperties.length; i++) {
+      for (int j = 0; j < WindowTabButtonProperties.PROPERTIES.getPropertyCount(); j++) {
+        Property property = WindowTabButtonProperties.PROPERTIES.getProperty(j);
+
+        // Highlighted properties inherits from normal properties
+        buttonProperties[i].get(tabProperties.getHighlightedButtonProperties().getMap()).
+            createRelativeRef(property,
+                              buttonProperties[i].get(tabProperties.getNormalButtonProperties().getMap()),
+                              property);
+
+        // Focus properties inherits from highlight properties
+        buttonProperties[i].get(tabProperties.getFocusedButtonProperties().getMap()).
+            createRelativeRef(property,
+                              buttonProperties[i].get(tabProperties.getHighlightedButtonProperties().getMap()),
+                              property);
+      }
+    }
+
+    tabProperties.getTitledTabProperties().getNormalProperties()
+        .setToolTipEnabled(true)
+        .getComponentProperties().setInsets(new Insets(0, 3, 0, 2));
+
+    tabProperties.getTitledTabProperties().setSizePolicy(TitledTabSizePolicy.INDIVIDUAL_SIZE);
+
+    tabProperties.getNormalButtonProperties().getCloseButtonProperties()
+        .setFactory(DefaultButtonFactories.getCloseButtonFactory())
+        .setTo(CloseWithAbortWindowAction.INSTANCE);
+
+    tabProperties.getNormalButtonProperties().getUndockButtonProperties()
+        .setFactory(DefaultButtonFactories.getUndockButtonFactory())
+        .setVisible(false)
+        .setTo(UndockWithAbortWindowAction.INSTANCE);
+
+    tabProperties.getNormalButtonProperties().getDockButtonProperties()
+        .setFactory(DefaultButtonFactories.getDockButtonFactory())
+        .setVisible(false)
+        .setTo(DockWithAbortWindowAction.INSTANCE);
+
+    tabProperties.getNormalButtonProperties().getRestoreButtonProperties()
+        .setFactory(DefaultButtonFactories.getRestoreButtonFactory())
+        .setVisible(false)
+        .setTo(RestoreWithAbortWindowAction.INSTANCE);
+
+    tabProperties.getNormalButtonProperties().getMinimizeButtonProperties()
+        .setFactory(DefaultButtonFactories.getMinimizeButtonFactory())
+        .setVisible(false)
+        .setTo(MinimizeWithAbortWindowAction.INSTANCE);
+
+    tabProperties.getTitledTabProperties().setFocusable(false);
+    tabProperties.getHighlightedButtonProperties().getCloseButtonProperties().setVisible(true);
+    tabProperties.getHighlightedButtonProperties().getMinimizeButtonProperties().setVisible(true);
+    tabProperties.getHighlightedButtonProperties().getRestoreButtonProperties().setVisible(true);
+    tabProperties.getHighlightedButtonProperties().getUndockButtonProperties().setVisible(true);
+    tabProperties.getHighlightedButtonProperties().getDockButtonProperties().setVisible(true);
+  }
+
+  private static void setTabbedPanelProperties() {
+    TabWindowProperties tabWindowProperties = DEFAULT_VALUES.getTabWindowProperties();
+
+    tabWindowProperties.getTabbedPanelProperties()
+        .setTabDropDownListVisiblePolicy(TabDropDownListVisiblePolicy.TABS_NOT_VISIBLE)
+        .setTabSelectTrigger(TabSelectTrigger.MOUSE_RELEASE)
+        .setEnsureSelectedTabVisible(true)
+        .setTabReorderEnabled(false)
+        .setHighlightPressedTab(false)
+        .setShadowEnabled(true);
+
+    tabWindowProperties.getTabbedPanelProperties().getTabAreaComponentsProperties().getComponentProperties()
+        .setInsets(new Insets(1, 3, 1, 3));
+
+    tabWindowProperties.getCloseButtonProperties()
+        .setFactory(DefaultButtonFactories.getCloseButtonFactory())
+        .setVisible(true)
+        .setTo(CloseWithAbortWindowAction.INSTANCE);
+
+    tabWindowProperties.getRestoreButtonProperties()
+        .setFactory(DefaultButtonFactories.getRestoreButtonFactory())
+        .setVisible(true)
+        .setTo(RestoreWithAbortWindowAction.INSTANCE);
+
+    tabWindowProperties.getMinimizeButtonProperties()
+        .setFactory(DefaultButtonFactories.getMinimizeButtonFactory())
+        .setVisible(true)
+        .setTo(MinimizeWithAbortWindowAction.INSTANCE);
+
+    tabWindowProperties.getMaximizeButtonProperties()
+        .setFactory(DefaultButtonFactories.getMaximizeButtonFactory())
+        .setVisible(true)
+        .setTo(MaximizeWithAbortWindowAction.INSTANCE);
+
+    tabWindowProperties.getUndockButtonProperties()
+        .setFactory(DefaultButtonFactories.getUndockButtonFactory())
+        .setVisible(true)
+        .setTo(UndockWithAbortWindowAction.INSTANCE);
+
+    tabWindowProperties.getDockButtonProperties()
+        .setFactory(DefaultButtonFactories.getDockButtonFactory())
+        .setVisible(true)
+        .setTo(DockWithAbortWindowAction.INSTANCE);
+
+    TabbedPanelButtonProperties buttonProps = tabWindowProperties.getTabbedPanelProperties().getButtonProperties();
+    buttonProps.getTabDropDownListButtonProperties().setToolTipText("Tab List");
+    buttonProps.getScrollLeftButtonProperties().setToolTipText("Scroll Left");
+    buttonProps.getScrollRightButtonProperties().setToolTipText("Scroll Right");
+    buttonProps.getScrollUpButtonProperties().setToolTipText("Scroll Up");
+    buttonProps.getScrollDownButtonProperties().setToolTipText("Scroll Down");
+  }
+
+  private static void setWindowBarProperties() {
+    {
+      WindowBarProperties p = DEFAULT_VALUES.getWindowBarProperties();
+
+      p.setMinimumWidth(4);
+      p.setContentPanelEdgeResizeEdgeDistance(6);
+      p.setContinuousLayoutEnabled(true);
+      p.setDragIndicatorColor(Color.DARK_GRAY);
+
+      p.getTabWindowProperties().getTabbedPanelProperties()
+          .setTabDeselectable(true)
+          .setAutoSelectTab(false)
+
+          .getTabAreaComponentsProperties()
+          .setStretchEnabled(true)
+
+          .getComponentProperties()
+          .setBorder(new TabAreaLineBorder());
+
+      p.getTabWindowProperties().getTabbedPanelProperties().getContentPanelProperties().getComponentProperties()
+          .setInsets(new Insets(4, 4, 4, 4));
+
+      p.getTabWindowProperties().getTabbedPanelProperties().getContentPanelProperties().getShapedPanelProperties()
+          .setOpaque(true);
+
+      p.getTabWindowProperties().getTabbedPanelProperties().getTabAreaProperties().setTabAreaVisiblePolicy(
+          TabAreaVisiblePolicy.ALWAYS);
+    }
+
+    {
+      WindowTabProperties p = DEFAULT_VALUES.getWindowBarProperties().getTabWindowProperties().getTabProperties();
+
+      p.getTitledTabProperties()
+          .setSizePolicy(TitledTabSizePolicy.EQUAL_SIZE)
+//          .addSuperObject(HighlightedTabSetup.createTabProperties())
+          .setHighlightedRaised(0);
+
+/*      p.getFocusedProperties()
+          .setBackgroundColor(Color.YELLOW);
+
+  */
+      p.getTitledTabProperties().getNormalProperties()
+          .getComponentProperties().setInsets(new Insets(1, 4, 1, 4));
+
+      p.getNormalButtonProperties().getCloseButtonProperties().setVisible(true);
+      p.getNormalButtonProperties().getRestoreButtonProperties().setVisible(true);
+      p.getNormalButtonProperties().getUndockButtonProperties().setVisible(true);
+      p.getNormalButtonProperties().getDockButtonProperties().setVisible(true);
+    }
+  }
+
+  private static void setFloatingWindowProperties() {
+    for (int i = 0; i < ComponentProperties.PROPERTIES.getPropertyCount(); i++) {
+      Property property = ComponentProperties.PROPERTIES.getProperty(i);
+
+      FloatingWindowProperties.COMPONENT_PROPERTIES.get(DEFAULT_VALUES.getFloatingWindowProperties().getMap()).
+          createRelativeRef(property,
+                            RootWindowProperties.WINDOW_AREA_PROPERTIES.get(DEFAULT_VALUES.getMap()),
+                            property);
+    }
+    for (int i = 0; i < ShapedPanelProperties.PROPERTIES.getPropertyCount(); i++) {
+      Property property = ShapedPanelProperties.PROPERTIES.getProperty(i);
+
+      FloatingWindowProperties.SHAPED_PANEL_PROPERTIES.get(DEFAULT_VALUES.getFloatingWindowProperties().getMap()).
+          createRelativeRef(property,
+                            RootWindowProperties.WINDOW_AREA_SHAPED_PANEL_PROPERTIES.get(DEFAULT_VALUES.getMap()),
+                            property);
+    }
+
+    DEFAULT_VALUES.getFloatingWindowProperties().setAutoCloseEnabled(true);
+    DEFAULT_VALUES.getFloatingWindowProperties().setUseFrame(false);
+  }
+
+  private static void setViewTitleBarProperties() {
+    ViewTitleBarProperties props = DEFAULT_VALUES.getViewProperties().getViewTitleBarProperties();
+
+    props.setOrientation(Direction.UP).setDirection(Direction.RIGHT).getNormalProperties().setTitleVisible(true)
+        .setIconVisible(true);
+
+    ViewTitleBarStateProperties stateProps = props.getNormalProperties();
+
+    stateProps.setButtonSpacing(0);
+
+    stateProps.getUndockButtonProperties()
+        .setFactory(DefaultButtonFactories.getUndockButtonFactory())
+        .setVisible(true)
+        .setTo(UndockWithAbortWindowAction.INSTANCE);
+
+    stateProps.getDockButtonProperties()
+        .setFactory(DefaultButtonFactories.getDockButtonFactory())
+        .setVisible(true)
+        .setTo(DockWithAbortWindowAction.INSTANCE);
+
+    stateProps.getCloseButtonProperties()
+        .setFactory(DefaultButtonFactories.getCloseButtonFactory())
+        .setVisible(true)
+        .setTo(CloseWithAbortWindowAction.INSTANCE);
+
+    stateProps.getRestoreButtonProperties()
+        .setFactory(DefaultButtonFactories.getRestoreButtonFactory())
+        .setVisible(true)
+        .setTo(RestoreViewWithAbortTitleBarAction.INSTANCE);
+
+    stateProps.getMinimizeButtonProperties()
+        .setFactory(DefaultButtonFactories.getMinimizeButtonFactory())
+        .setVisible(true)
+        .setTo(MinimizeWithAbortWindowAction.INSTANCE);
+
+    stateProps.getMaximizeButtonProperties()
+        .setFactory(DefaultButtonFactories.getMaximizeButtonFactory())
+        .setVisible(true)
+        .setTo(MaximizeWithAbortWindowAction.INSTANCE);
+
+    stateProps.getMap().createRelativeRef(ViewTitleBarStateProperties.TITLE,
+                                          DEFAULT_VALUES.getViewProperties().getMap(),
+                                          ViewProperties.TITLE);
+
+    stateProps.getMap().createRelativeRef(ViewTitleBarStateProperties.ICON,
+                                          DEFAULT_VALUES.getViewProperties().getMap(),
+                                          ViewProperties.ICON);
+  }
+
+  private static void updateVisualProperties() {
+    DEFAULT_VALUES.getWindowBarProperties().getTabWindowProperties().getTabProperties().getTitledTabProperties()
+        .getNormalProperties().getComponentProperties().setBackgroundColor(
+        TabbedUIDefaults.getHighlightedStateBackground());
+
+    Color shadowColor = UIManagerUtil.getColor("controlDkShadow", Color.BLACK);
+
+    DEFAULT_VALUES.getWindowAreaProperties()
+        .setBorder(new LineBorder(shadowColor))
+        .setBackgroundColor(UIManagerUtil.getColor("Desktop.background", "control"));
+
+    DEFAULT_VALUES.getWindowAreaShapedPanelProperties().setOpaque(true);
+
+    DEFAULT_VALUES.getDragLabelProperties()
+        .setBorder(new LineBorder(shadowColor))
+        .setFont(UIManagerUtil.getFont("ToolTip.font"))
+        .setForegroundColor(UIManagerUtil.getColor("ToolTip.foreground", "controlText"))
+        .setBackgroundColor(UIManagerUtil.getColor("ToolTip.background", "control"));
+
+    DEFAULT_VALUES
+        .setRecursiveTabsEnabled(true);
+  }
+
+  private static void updateFont() {
+    Font font = TitledTabProperties.getDefaultProperties().getHighlightedProperties().
+        getComponentProperties().getFont();
+
+    if (font != null)
+      font = font.deriveFont(Font.BOLD);
+
+    DEFAULT_VALUES.getTabWindowProperties().getTabProperties().getTitledTabProperties().
+        getHighlightedProperties().getComponentProperties().setFont(font);
+  }
+
+  private static void updateViewTitleBarProperties() {
+    ViewTitleBarProperties props = DEFAULT_VALUES.getViewProperties().getViewTitleBarProperties();
+    Font font = TabbedUIDefaults.getFont();
+    if (font != null)
+      font = font.deriveFont(Font.BOLD);
+
+
+    props.getNormalProperties().getComponentProperties().setFont(font).setForegroundColor(
+        UIManager.getColor("InternalFrame.inactiveTitleForeground"))
+        .setBackgroundColor(UIManager.getColor("InternalFrame.inactiveTitleBackground"))
+        .setInsets(new Insets(2, 2, 2, 2));
+    props.getFocusedProperties().getComponentProperties().setForegroundColor(
+        UIManager.getColor("InternalFrame.activeTitleForeground"))
+        .setBackgroundColor(UIManager.getColor("InternalFrame.activeTitleBackground"));
+
+    Color c1 = UIManager.getColor("InternalFrame.inactiveTitleBackground");
+    Color c2 = UIManager.getColor("InternalFrame.inactiveTitleGradient");
+    ComponentPainter backgroundPainter;
+
+    if (c1 == null)
+      backgroundPainter = null;
+    else if (c1.equals(c2) || c2 == null)
+      backgroundPainter = new SolidColorComponentPainter(new FixedColorProvider(c1));
+    else
+      backgroundPainter = new GradientComponentPainter(c2, c2, c1, c1);
+    props.getNormalProperties().getShapedPanelProperties().setComponentPainter(backgroundPainter).setOpaque(true);
+
+    Color focused1 = UIManager.getColor("InternalFrame.activeTitleBackground");
+    Color focused2 = UIManager.getColor("InternalFrame.activeTitleGradient");
+    ComponentPainter focusedPainter;
+
+    if (focused1 == null)
+      focusedPainter = null;
+    else if (focused1.equals(focused2) || focused2 == null)
+      focusedPainter = new SolidColorComponentPainter(new FixedColorProvider(focused1));
+    else
+      focusedPainter = new GradientComponentPainter(focused2, focused2, focused1, focused1);
+    props.getFocusedProperties().getShapedPanelProperties().setComponentPainter(focusedPainter);
+    props.getFocusedProperties().getComponentProperties().setForegroundColor(
+        UIManager.getColor("InternalFrame.activeTitleForeground"));
+
+    props.setContentTitleBarGap(0).getNormalProperties().setIconTextGap(TabbedUIDefaults.getIconTextGap());
+  }
+
+  static {
+    DEFAULT_VALUES = new RootWindowProperties(PROPERTIES.getDefaultMap());
+
+    DEFAULT_VALUES
+        .setAbortDragKey(TabbedPanelProperties.getDefaultProperties().getAbortDragKey())
+        .setEdgeSplitDistance(6)
+        .setDragRectangleBorderWidth(5);
+
+    DEFAULT_VALUES.getShapedPanelProperties().setOpaque(true);
+
+    DEFAULT_VALUES.getDockingWindowProperties()
+        .setMaximizeEnabled(true)
+        .setMinimizeEnabled(true)
+        .setCloseEnabled(true)
+        .setRestoreEnabled(true)
+        .setDragEnabled(true)
+        .setUndockEnabled(true)
+        .setUndockOnDropEnabled(true)
+        .setDockEnabled(true);
+
+    DEFAULT_VALUES.getDockingWindowProperties().getDropFilterProperties()
+        .setChildDropFilter(AcceptAllDropFilter.INSTANCE)
+        .setInsertTabDropFilter(AcceptAllDropFilter.INSTANCE)
+        .setInteriorDropFilter(AcceptAllDropFilter.INSTANCE)
+        .setSplitDropFilter(AcceptAllDropFilter.INSTANCE);
+
+    DEFAULT_VALUES.getWindowAreaProperties()
+        .setInsets(new Insets(6, 6, 2, 2));
+
+    DEFAULT_VALUES.getDragLabelProperties()
+        .setInsets(new Insets(4, 6, 4, 6));
+
+    DEFAULT_VALUES.getDragRectangleShapedPanelProperties().setOpaque(false);
+
+    DEFAULT_VALUES.getSplitWindowProperties()
+        .setContinuousLayoutEnabled(true)
+        .setDividerSize(4)
+        .setDividerLocationDragEnabled(true)
+        .setDragIndicatorColor(Color.DARK_GRAY);
+
+    DEFAULT_VALUES.getViewProperties().setAlwaysShowTitle(true);
+
+    setTabbedPanelProperties();
+    setTabProperties();
+    setWindowBarProperties();
+    setViewTitleBarProperties();
+    setFloatingWindowProperties();
+
+    updateVisualProperties();
+
+    updateViewTitleBarProperties();
+
+    updateFont();
+
+    TitledTabProperties.getDefaultProperties().getHighlightedProperties().getComponentProperties().getMap().
+        addListener(new PropertyMapListener() {
+          public void propertyValuesChanged(PropertyMap propertyObject, Map changes) {
+            updateFont();
+          }
+        });
+
+    DynamicUIManager.getInstance().addListener(new DynamicUIManagerListener() {
+      public void lookAndFeelChanged() {
+        PropertyMapManager.runBatch(new Runnable() {
+          public void run() {
+            updateVisualProperties();
+            updateViewTitleBarProperties();
+          }
+        });
+      }
+
+      public void propertiesChanged() {
+        PropertyMapManager.runBatch(new Runnable() {
+          public void run() {
+            updateVisualProperties();
+            updateViewTitleBarProperties();
+          }
+        });
+      }
+
+      public void propertiesChanging() {
+      }
+
+      public void lookAndFeelChanging() {
+      }
+    });
+  }
+
+  /**
+   * Creates a property object that inherits default property values.
+   *
+   * @return a new property object that inherits default property values
+   */
+  public static RootWindowProperties createDefault() {
+    return new RootWindowProperties(DEFAULT_VALUES);
+  }
+
+  /**
+   * Creates an empty property object.
+   */
+  public RootWindowProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property object containing the map.
+   *
+   * @param map the property map
+   */
+  public RootWindowProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object which inherits property values from another object.
+   *
+   * @param inheritFrom the object which from to inherit property values
+   */
+  public RootWindowProperties(RootWindowProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public RootWindowProperties addSuperObject(RootWindowProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   * @since IDW 1.1.0
+   * @deprecated Use {@link #removeSuperObject(RootWindowProperties)} instead.
+   */
+  public RootWindowProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public RootWindowProperties removeSuperObject(RootWindowProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Replaces a super object.
+   *
+   * @param oldSuperObject the super object to be replaced
+   * @param newSuperObject the super object to replace it with
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public RootWindowProperties replaceSuperObject(RootWindowProperties oldSuperObject,
+                                                 RootWindowProperties newSuperObject) {
+    getMap().replaceSuperMap(oldSuperObject.getMap(), newSuperObject.getMap());
+    return this;
+  }
+
+  /**
+   * Returns the default property values for tab windows.
+   *
+   * @return the default property values for tab windows
+   */
+  public TabWindowProperties getTabWindowProperties() {
+    return new TabWindowProperties(TAB_WINDOW_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the default property values for split windows.
+   *
+   * @return the default property values for split windows
+   */
+  public SplitWindowProperties getSplitWindowProperties() {
+    return new SplitWindowProperties(SPLIT_WINDOW_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the default property values for floating windows.
+   *
+   * @return the default property values for floating windows
+   * @since IDW 1.4.0
+   */
+  public FloatingWindowProperties getFloatingWindowProperties() {
+    return new FloatingWindowProperties(FLOATING_WINDOW_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the default property values for views.
+   *
+   * @return the default property values for views
+   */
+  public ViewProperties getViewProperties() {
+    return new ViewProperties(VIEW_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the default property values for docking windows.
+   *
+   * @return the default property values for docking windows
+   */
+  public DockingWindowProperties getDockingWindowProperties() {
+    return new DockingWindowProperties(DOCKING_WINDOW_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Sets the border width of the drag rectangle.
+   *
+   * @param width the border width
+   * @return this
+   */
+  public RootWindowProperties setDragRectangleBorderWidth(int width) {
+    DRAG_RECTANGLE_BORDER_WIDTH.set(getMap(), width);
+    return this;
+  }
+
+  /**
+   * Returns the border width of the drag rectangle.
+   *
+   * @return the border width of the drag rectangle
+   */
+  public int getDragRectangleBorderWidth() {
+    return DRAG_RECTANGLE_BORDER_WIDTH.get(getMap());
+  }
+
+  /**
+   * Returns true if the user is allowed to place tab windows inside other tab windows.
+   *
+   * @return true if tab windows are allowed to be placed in other tab windows
+   */
+  public boolean getRecursiveTabsEnabled() {
+    return RECURSIVE_TABS_ENABLED.get(getMap());
+  }
+
+  /**
+   * Returns true if double clicking on a window tab in a window bar restores the window.
+   *
+   * @return true if double clicking on a window tab in a window bar restores the window
+   */
+  public boolean getDoubleClickRestoresWindow() {
+    return DOUBLE_CLICK_RESTORES_WINDOW.get(getMap());
+  }
+
+  /**
+   * If set to true, double clicking on a window tab in a window bar restores the window.
+   *
+   * @param enabled if true, double clicking on a window tab in a window bar restores the window
+   * @return this
+   */
+  public RootWindowProperties setDoubleClickRestoresWindow(boolean enabled) {
+    DOUBLE_CLICK_RESTORES_WINDOW.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * If set to true, the user is allowed to place tab windows inside other tab windows.
+   *
+   * @param enabled if true, the user is allowed to place tab windows inside other tab windows
+   * @return this
+   */
+  public RootWindowProperties setRecursiveTabsEnabled(boolean enabled) {
+    RECURSIVE_TABS_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Returns the property values for the drag label.
+   *
+   * @return the property values for the drag label
+   */
+  public ComponentProperties getDragLabelProperties() {
+    return new ComponentProperties(DRAG_LABEL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the property values for the root window component.
+   *
+   * @return the property values for the root window component
+   */
+  public ComponentProperties getComponentProperties() {
+    return new ComponentProperties(COMPONENT_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the property values for the root window shaped panel.
+   *
+   * @return the property values for the root window shaped panel
+   * @since IDW 1.2.0
+   */
+  public ShapedPanelProperties getShapedPanelProperties() {
+    return new ShapedPanelProperties(SHAPED_PANEL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the component property values for the window area component.
+   *
+   * @return the component property values for the window area component
+   */
+  public ComponentProperties getWindowAreaProperties() {
+    return new ComponentProperties(WINDOW_AREA_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the shaped panel property values for the window area component.
+   *
+   * @return the shaped panel property values for the window area component
+   */
+  public ShapedPanelProperties getWindowAreaShapedPanelProperties() {
+    return new ShapedPanelProperties(WINDOW_AREA_SHAPED_PANEL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Sets the distance from the window edge inside which a mouse drag will trigger a window split.
+   *
+   * @param size the distance from the window edge inside which a mouse drag will trigger a window split
+   * @return this
+   */
+  public RootWindowProperties setEdgeSplitDistance(int size) {
+    EDGE_SPLIT_DISTANCE.set(getMap(), size);
+    return this;
+  }
+
+  /**
+   * Returns the distance from the window edge inside which a mouse drag will trigger a window split.
+   *
+   * @return the distance from the window edge inside which a mouse drag will trigger a window split
+   */
+  public int getEdgeSplitDistance() {
+    return EDGE_SPLIT_DISTANCE.get(getMap());
+  }
+
+  /**
+   * Returns the key code for the key that aborts a drag.
+   *
+   * @return the key code for the key that aborts a drag
+   */
+  public int getAbortDragKey() {
+    return ABORT_DRAG_KEY.get(getMap());
+  }
+
+  /**
+   * Sets the key code for the key that aborts a drag.
+   *
+   * @param key the key code for the key that aborts a drag
+   * @return this
+   */
+  public RootWindowProperties setAbortDragKey(int key) {
+    ABORT_DRAG_KEY.set(getMap(), key);
+    return this;
+  }
+
+  /**
+   * Returns the default window bar property values.
+   *
+   * @return the default window bar property values
+   * @since IDW 1.1.0
+   */
+  public WindowBarProperties getWindowBarProperties() {
+    return new WindowBarProperties(WINDOW_BAR_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Shaped panel properties for the drag rectangle. Setting a painter disables the default drag rectangle.
+   *
+   * @return the drag rectangle shaped panel properties
+   * @since IDW 1.2.0
+   */
+  public ShapedPanelProperties getDragRectangleShapedPanelProperties() {
+    return new ShapedPanelProperties(DRAG_RECTANGLE_SHAPED_PANEL_PROPERTIES.get(getMap()));
+  }
+
+}
diff --git a/src/net/infonode/docking/properties/SplitWindowProperties.java b/src/net/infonode/docking/properties/SplitWindowProperties.java
new file mode 100644
index 0000000..36a294f
--- /dev/null
+++ b/src/net/infonode/docking/properties/SplitWindowProperties.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SplitWindowProperties.java,v 1.20 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+import net.infonode.properties.types.ColorProperty;
+import net.infonode.properties.types.IntegerProperty;
+
+import java.awt.*;
+
+/**
+ * Properties and property values for split windows.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.20 $
+ */
+public class SplitWindowProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all split window properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Split Window Properties", "");
+
+  /**
+   * When enabled causes the windows to change size continuously while dragging the split window divider.
+   *
+   * @since IDW 1.1.0
+   */
+  public static final BooleanProperty CONTINUOUS_LAYOUT_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Continuous Layout Enabled",
+                          "When enabled causes the windows to change size continuously while dragging the split window divider.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The split pane divider size.
+   */
+  public static final IntegerProperty DIVIDER_SIZE =
+      IntegerProperty.createPositive(PROPERTIES,
+                                     "Divider Size",
+                                     "The split pane divider size.",
+                                     2,
+                                     PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * When enabled the user can drag the SplitWindow divider to a new location.
+   *
+   * @since IDW 1.2.0
+   */
+  public static final BooleanProperty DIVIDER_LOCATION_DRAG_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Divider Location Drag Enabled",
+                          "When enabled the user can drag the SplitWindow divider to a new location.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The split pane drag indicator color.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final ColorProperty DRAG_INDICATOR_COLOR =
+      new ColorProperty(PROPERTIES,
+                        "Drag Indicator Color",
+                        "The color for the divider's drag indicator that is shown when continuous layout is disabled.",
+                        PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Creates an empty property object.
+   */
+  public SplitWindowProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property map containing the map.
+   *
+   * @param map the property map
+   */
+  public SplitWindowProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public SplitWindowProperties(SplitWindowProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public SplitWindowProperties addSuperObject(SplitWindowProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   * @since IDW 1.1.0
+   * @deprecated Use {@link #removeSuperObject(SplitWindowProperties)} instead.
+   */
+  public SplitWindowProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public SplitWindowProperties removeSuperObject(SplitWindowProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Sets the split pane divider size.
+   *
+   * @param size the split pane divider size
+   * @return this
+   */
+  public SplitWindowProperties setDividerSize(int size) {
+    DIVIDER_SIZE.set(getMap(), size);
+    return this;
+  }
+
+  /**
+   * Returns the split pane divider size.
+   *
+   * @return the split pane divider size
+   */
+  public int getDividerSize() {
+    return DIVIDER_SIZE.get(getMap());
+  }
+
+  /**
+   * Sets the split pane drag indicator color.
+   *
+   * @param color the color for the drag indicator
+   * @return this
+   * @since IDW 1.4.0
+   */
+  public SplitWindowProperties setDragIndicatorColor(Color color) {
+    DRAG_INDICATOR_COLOR.set(getMap(), color);
+    return this;
+  }
+
+  /**
+   * Returns the split pane drag indicator color.
+   *
+   * @return the split pane drag indicator color
+   * @since IDW 1.4.0
+   */
+  public Color getDragIndicatorColor() {
+    return DRAG_INDICATOR_COLOR.get(getMap());
+  }
+
+  /**
+   * Returns true if continuous layout is enabled.
+   *
+   * @return true if continuous layout is enabled
+   * @since IDW 1.1.0
+   */
+  public boolean getContinuousLayoutEnabled() {
+    return CONTINUOUS_LAYOUT_ENABLED.get(getMap());
+  }
+
+  /**
+   * Enables/disables continuous layout.
+   *
+   * @param enabled if true continuous layout is enabled
+   * @return this
+   * @since IDW 1.1.0
+   */
+  public SplitWindowProperties setContinuousLayoutEnabled(boolean enabled) {
+    CONTINUOUS_LAYOUT_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Returns true if the user can drag the SplitWindow divider to a new location.
+   *
+   * @return true if the user can drag the SplitWindow divider to a new location
+   * @since IDW 1.2.0
+   */
+  public boolean getDividerLocationDragEnabled() {
+    return DIVIDER_LOCATION_DRAG_ENABLED.get(getMap());
+  }
+
+  /**
+   * When enabled the user can drag the SplitWindow divider to a new location.
+   *
+   * @param enabled if true the user can drag the SplitWindow divider to a new location
+   * @return this
+   * @since IDW 1.2.0
+   */
+  public SplitWindowProperties setDividerLocationDragEnabled(boolean enabled) {
+    DIVIDER_LOCATION_DRAG_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+}
diff --git a/src/net/infonode/docking/properties/TabWindowProperties.java b/src/net/infonode/docking/properties/TabWindowProperties.java
new file mode 100644
index 0000000..6f0655f
--- /dev/null
+++ b/src/net/infonode/docking/properties/TabWindowProperties.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabWindowProperties.java,v 1.22 2009/02/24 13:49:23 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+
+/**
+ * Properties and property values for tab windows.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.22 $
+ */
+public class TabWindowProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all tab window properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Tab Window Properties", "");
+
+  /**
+   * Property values for the tabbed panel in the tab window.
+   */
+  public static final PropertyMapProperty TABBED_PANEL_PROPERTIES =
+    new PropertyMapProperty(PROPERTIES,
+        "Tabbed Panel Properties",
+        "Property values for the tabbed panel in the TabWindow.",
+        TabbedPanelProperties.PROPERTIES);
+
+  /**
+   * Default property values for the window tabs in the tab window.
+   */
+  public static final PropertyMapProperty TAB_PROPERTIES =
+    new PropertyMapProperty(PROPERTIES,
+        "Tab Properties",
+        "Default property values for the window tabs in the TabWindow.",
+        WindowTabProperties.PROPERTIES);
+
+  /**
+   * The minimize button property values.
+   *
+   * @since IDW 1.1.0
+   */
+  public static final PropertyMapProperty MINIMIZE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+      "Minimize Button Properties",
+      "The minimize button property values.",
+      WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The restore button property values.
+   *
+   * @since IDW 1.1.0
+   */
+  public static final PropertyMapProperty RESTORE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+      "Restore Button Properties",
+      "The restore button property values.",
+      WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The close button property values.
+   *
+   * @since IDW 1.1.0
+   */
+  public static final PropertyMapProperty CLOSE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+      "Close Button Properties",
+      "The close button property values.",
+      WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The maximize button property values.
+   *
+   * @since IDW 1.1.0
+   */
+  public static final PropertyMapProperty MAXIMIZE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+      "Maximize Button Properties",
+      "The maximize button property values.",
+      WindowTabButtonProperties.PROPERTIES);
+
+
+  /**
+   * The undock button property values.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final PropertyMapProperty UNDOCK_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+      "Undock Button Properties",
+      "The undock button property values.",
+      WindowTabButtonProperties.PROPERTIES);
+
+
+  /**
+   * The dock button property values.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final PropertyMapProperty DOCK_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+      "Dock Button Properties",
+      "The dock button property values.",
+      WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The respect child windows minimum sizes property.
+   *
+   * @since IDW 1.5.0
+   */
+  public static final BooleanProperty RESPECT_CHILD_WINDOW_MINIMUM_SIZE =
+    new BooleanProperty(PROPERTIES,
+        "Respect Child Window Minimum Size",
+        "When enabled the Tab Window will respect its child windows minimum sizes.",
+        PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Creates an empty property object.
+   */
+  public TabWindowProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property object containing the map.
+   *
+   * @param map the property map
+   */
+  public TabWindowProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public TabWindowProperties(TabWindowProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public TabWindowProperties addSuperObject(TabWindowProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   * @since IDW 1.1.0
+   * @deprecated Use {@link #removeSuperObject(TabWindowProperties)} instead.
+   */
+  public TabWindowProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public TabWindowProperties removeSuperObject(TabWindowProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Returns the property values for the tabbed panel in the tab window.
+   *
+   * @return the property values for the tabbed panel in the tab window
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return new TabbedPanelProperties(TABBED_PANEL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the default property values for the window tabs in the tab window.
+   *
+   * @return the default property values for the window tabs in the tab window
+   */
+  public WindowTabProperties getTabProperties() {
+    return new WindowTabProperties(TAB_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the minimize button property values.
+   *
+   * @return the minimize button property values
+   * @since IDW 1.1.0
+   */
+  public WindowTabButtonProperties getMinimizeButtonProperties() {
+    return new WindowTabButtonProperties(MINIMIZE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the restore button property values.
+   *
+   * @return the restore button property values
+   * @since IDW 1.1.0
+   */
+  public WindowTabButtonProperties getRestoreButtonProperties() {
+    return new WindowTabButtonProperties(RESTORE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the close button property values.
+   *
+   * @return the close button property values
+   * @since IDW 1.1.0
+   */
+  public WindowTabButtonProperties getCloseButtonProperties() {
+    return new WindowTabButtonProperties(CLOSE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the maximize button property values.
+   *
+   * @return the maximize button property values
+   * @since IDW 1.1.0
+   */
+  public WindowTabButtonProperties getMaximizeButtonProperties() {
+    return new WindowTabButtonProperties(MAXIMIZE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the undock button property values.
+   *
+   * @return the undock button property values
+   * @since IDW 1.4.0
+   */
+  public WindowTabButtonProperties getUndockButtonProperties() {
+    return new WindowTabButtonProperties(UNDOCK_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the dock button property values.
+   *
+   * @return the dock button property values
+   * @since IDW 1.4.0
+   */
+  public WindowTabButtonProperties getDockButtonProperties() {
+    return new WindowTabButtonProperties(DOCK_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * <p>
+   * Returns true if the TabWindow will respect its child windows minimum sizes.
+   * </p>
+   *
+   * <p>
+   * When true the content area of the TabWindow cannot be resized smaller than
+   * the maximum of all its child windows minimum sizes.
+   * </p>
+   *
+   * @return true if the TabWindow respects its child windows minimum sizes
+   * @since IDW 1.5.0
+   */
+  public boolean getRespectChildWindowMinimumSize() {
+    return RESPECT_CHILD_WINDOW_MINIMUM_SIZE.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Enables/disables the TabWindow will respect its child windows minimum sizes.
+   * </p>
+   *
+   * <p>
+   * When true the content area of the TabWindow cannot be resized smaller than
+   * the maximum of all its child windows minimum sizes.
+   * </p>
+   *
+   * @param repsectMinimuSize if true the TabWindow will respect its child windows
+   *                          minimum sizes
+   * @return this
+   * @since IDW 1.5.0
+   */
+  public TabWindowProperties setRespectChildWindowMinimumSize(boolean repsectMinimuSize) {
+    RESPECT_CHILD_WINDOW_MINIMUM_SIZE.set(getMap(), repsectMinimuSize);
+    return this;
+  }
+
+}
diff --git a/src/net/infonode/docking/properties/ViewProperties.java b/src/net/infonode/docking/properties/ViewProperties.java
new file mode 100644
index 0000000..19fb524
--- /dev/null
+++ b/src/net/infonode/docking/properties/ViewProperties.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+// $Id: ViewProperties.java,v 1.21 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+import net.infonode.properties.types.IconProperty;
+import net.infonode.properties.types.StringProperty;
+
+import javax.swing.*;
+
+/**
+ * Properties and property values for views.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.21 $
+ */
+public class ViewProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all view properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("View Properties", "");
+
+  /**
+   * Properties for the view title bar
+   *
+   * @see #getViewTitleBarProperties
+   * @since IDW 1.4.0
+   */
+  public static final PropertyMapProperty VIEW_TITLE_BAR_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                              "View Title Bar Properties",
+                                                                                              "Properties for view's title bar.",
+                                                                                              ViewTitleBarProperties.PROPERTIES);
+
+  /**
+   * If true the view will always be placed in a TabWindow so that it's title is shown.
+   */
+  public static final BooleanProperty ALWAYS_SHOW_TITLE =
+      new BooleanProperty(PROPERTIES,
+                          "Always Show Title",
+                          "If true the view will always be placed in a TabWindow so that it's title is shown.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The view title.
+   */
+  public static final StringProperty TITLE = new StringProperty(PROPERTIES,
+                                                                "Title",
+                                                                "The view title.",
+                                                                PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The view icon.
+   */
+  public static final IconProperty ICON = new IconProperty(PROPERTIES,
+                                                           "Icon",
+                                                           "The view icon.",
+                                                           PropertyMapValueHandler.INSTANCE);
+
+  static {
+    new ViewProperties(PROPERTIES.getDefaultMap()).setAlwaysShowTitle(true);
+  }
+
+  /**
+   * Creates an empty property object.
+   */
+  public ViewProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property object containing the map.
+   *
+   * @param map the property map
+   */
+  public ViewProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public ViewProperties(ViewProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public ViewProperties addSuperObject(ViewProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   * @since IDW 1.1.0
+   * @deprecated Use {@link #removeSuperObject(ViewProperties)} instead.
+   */
+  public ViewProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public ViewProperties removeSuperObject(ViewProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Returns the property values for the title bar in the view
+   *
+   * @return the property values for the title bar in the view
+   * @since IDW 1.4.0
+   */
+  public ViewTitleBarProperties getViewTitleBarProperties() {
+    return new ViewTitleBarProperties(VIEW_TITLE_BAR_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns true if the view shows it's title even though it's not in a tabbed panel with other windows.
+   *
+   * @return true if the view shows it's title even though it's not in a tabbed panel with other windows
+   */
+  public boolean getAlwaysShowTitle() {
+    return ALWAYS_SHOW_TITLE.get(getMap());
+  }
+
+  /**
+   * Set to true the view should always be placed in a TabWindow so that it's title is shown.
+   *
+   * @param showTitle true the view should always be placed in a TabWindow so that it's title is shown
+   * @return this
+   */
+  public ViewProperties setAlwaysShowTitle(boolean showTitle) {
+    ALWAYS_SHOW_TITLE.set(getMap(), showTitle);
+
+    return this;
+  }
+
+  /**
+   * Sets the view title.
+   *
+   * @param title the view title
+   * @return this
+   */
+  public ViewProperties setTitle(String title) {
+    TITLE.set(getMap(), title);
+
+    return this;
+  }
+
+  /**
+   * Sets the view icon.
+   *
+   * @param icon the view icon
+   * @return this
+   */
+  public ViewProperties setIcon(Icon icon) {
+    ICON.set(getMap(), icon);
+
+    return this;
+  }
+
+  /**
+   * Returns the view title.
+   *
+   * @return the view title
+   */
+  public String getTitle() {
+    return TITLE.get(getMap());
+  }
+
+  /**
+   * Returns the view icon.
+   *
+   * @return the view icon
+   */
+  public Icon getIcon() {
+    return ICON.get(getMap());
+  }
+}
diff --git a/src/net/infonode/docking/properties/ViewTitleBarProperties.java b/src/net/infonode/docking/properties/ViewTitleBarProperties.java
new file mode 100644
index 0000000..7f7c0eb
--- /dev/null
+++ b/src/net/infonode/docking/properties/ViewTitleBarProperties.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ViewTitleBarProperties.java,v 1.14 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.gui.DimensionProvider;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.*;
+import net.infonode.util.Direction;
+
+/**
+ * Properties and property values for a view title bar.
+ *
+ * @author johan
+ * @version $Revision: 1.14 $
+ * @since IDW 1.4.0
+ */
+public class ViewTitleBarProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all view title bar properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("View Title Bar Properties", "");
+
+  /**
+   * Normal properties
+   *
+   * @see #getNormalProperties
+   */
+  public static final PropertyMapProperty NORMAL_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                      "Normal Properties",
+                                                                                      "Properties for title bar.",
+                                                                                      ViewTitleBarStateProperties.PROPERTIES);
+
+  /**
+   * Focused properties. Added as super object to normal properties when view has focus
+   *
+   * @see #getFocusedProperties
+   */
+  public static final PropertyMapProperty FOCUSED_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                       "Focused Properties",
+                                                                                       "Properties for title bar.",
+                                                                                       ViewTitleBarStateProperties.PROPERTIES);
+
+  /**
+   * Visible property
+   *
+   * @see #setVisible
+   * @see #getVisible
+   */
+  public static final BooleanProperty VISIBLE = new BooleanProperty(PROPERTIES,
+                                                                    "Visible",
+                                                                    "Controls if the view title bar should be visible or not",
+                                                                    PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Title bar minimum size property
+   *
+   * @see #setMinimumSizeProvider(DimensionProvider)
+   * @see #getMinimumSizeProvider()
+   */
+  public static final DimensionProviderProperty MINIMUM_SIZE_PROVIDER = new DimensionProviderProperty(PROPERTIES,
+                                                                                                      "Minimum Size",
+                                                                                                      "Title bar minimum size.",
+                                                                                                      PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Content title bar gap property
+   *
+   * @see #setContentTitleBarGap
+   * @see #getContentTitleBarGap
+   */
+  public static final IntegerProperty CONTENT_TITLE_BAR_GAP = IntegerProperty.createPositive(PROPERTIES,
+                                                                                             "Content Title Bar Gap",
+                                                                                             "Gap in pixels between the view content and the title bar",
+                                                                                             2,
+                                                                                             PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Orientation property
+   *
+   * @see #setOrientation
+   * @see #getOrientation
+   */
+  public static final DirectionProperty ORIENTATION =
+      new DirectionProperty(PROPERTIES, "Orientation", "Title bar's orientation relative to the view's content area.",
+                            PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Direction property
+   *
+   * @see #setDirection
+   * @see #getDirection
+   */
+  public static final DirectionProperty DIRECTION =
+      new DirectionProperty(PROPERTIES, "Direction", "Title bar's layout direction.",
+                            PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Hover listener property
+   *
+   * @see #setHoverListener
+   * @see #getHoverListener
+   */
+  public static final HoverListenerProperty HOVER_LISTENER = new HoverListenerProperty(PROPERTIES,
+                                                                                       "Hover Listener",
+                                                                                       "Hover Listener to be used for tracking mouse hovering over the title bar.",
+                                                                                       PropertyMapValueHandler.INSTANCE);
+
+
+  /**
+   * Creates an empty property object.
+   */
+  public ViewTitleBarProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property object containing the map.
+   *
+   * @param map the property map
+   */
+  public ViewTitleBarProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public ViewTitleBarProperties(ViewTitleBarProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public ViewTitleBarProperties addSuperObject(ViewTitleBarProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   */
+  public ViewTitleBarProperties removeSuperObject(ViewTitleBarProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Returns the property values for the title bar's normal state
+   *
+   * @return the property values for the title bar's normal state
+   */
+  public ViewTitleBarStateProperties getNormalProperties() {
+    return new ViewTitleBarStateProperties(NORMAL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * <p>
+   * Returns the property values for the title bar's focused state
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong>These properties are added as super object to the normal
+   * properties when the view has focus.
+   * </p>
+   *
+   * @return the property values for the title bar's focused state
+   */
+  public ViewTitleBarStateProperties getFocusedProperties() {
+    return new ViewTitleBarStateProperties(FOCUSED_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Sets if the title bar should be visible or not
+   *
+   * @param visible True for visible, otherwise false
+   * @return this
+   */
+  public ViewTitleBarProperties setVisible(boolean visible) {
+    VISIBLE.set(getMap(), visible);
+    return this;
+  }
+
+  /**
+   * Returns if the title bar should be visible or not
+   *
+   * @return True if visible, otherwise false
+   */
+  public boolean getVisible() {
+    return VISIBLE.get(getMap());
+  }
+
+  /**
+   * Sets the title bar's minimum size dimension provider
+   *
+   * @param size the minimum size dimension provider or null if title bar's default minimum size should be used instead
+   * @return this ViewTitleBarProperties
+   */
+  public ViewTitleBarProperties setMinimumSizeProvider(DimensionProvider size) {
+    MINIMUM_SIZE_PROVIDER.set(getMap(), size);
+
+    return this;
+  }
+
+  /**
+   * Gets the dimension provider for the title bar's minimum size
+   *
+   * @return the minimum size provider or null if default title bar minimum size is to be used instead
+   */
+  public DimensionProvider getMinimumSizeProvider() {
+    return MINIMUM_SIZE_PROVIDER.get(getMap());
+  }
+
+  /**
+   * Sets the gap between the view's content and the title bar
+   *
+   * @param gap gap in pixels
+   * @return this
+   */
+  public ViewTitleBarProperties setContentTitleBarGap(int gap) {
+    CONTENT_TITLE_BAR_GAP.set(getMap(), gap);
+    return this;
+  }
+
+  /**
+   * Returns the gap between the view's content and the title bar
+   *
+   * @return gap in pixels
+   */
+  public int getContentTitleBarGap() {
+    return CONTENT_TITLE_BAR_GAP.get(getMap());
+  }
+
+  /**
+   * Sets the orientation i.e. on what side of the view's content the title bar will be placed
+   *
+   * @param orientation the orientation
+   * @return this
+   */
+  public ViewTitleBarProperties setOrientation(Direction orientation) {
+    ORIENTATION.set(getMap(), orientation);
+    return this;
+  }
+
+  /**
+   * Returns the orientation i.e. on what side of the view's content the title bar will be placed
+   *
+   * @return the orientation
+   */
+  public Direction getOrientation() {
+    return ORIENTATION.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Sets the layout direction
+   * </p>
+   *
+   * <p>
+   * The icon, text and components are laid out in a line that will be rotated in the given direction.
+   * The text and icon is rotated and the components are only moved.
+   * </p>
+   *
+   * @param direction the layout direction
+   * @return this
+   */
+  public ViewTitleBarProperties setDirection(Direction direction) {
+    DIRECTION.set(getMap(), direction);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Returns the layout direction
+   * </p>
+   *
+   * <p>
+   * The icon, text and components are laid out in a line that will be rotated in the given direction.
+   * The text and icon is rotated and the components are only moved.
+   * </p>
+   *
+   * @return the layout direction
+   */
+  public Direction getDirection() {
+    return DIRECTION.get(getMap());
+  }
+
+  /**
+   * <p>Sets the hover listener that will be triggered when the title bar is hoverd by the mouse.</p>
+   *
+   * <p>The view that contains the title bar will be the source of the hover event sent to the
+   * hover listener.</p>
+   *
+   * @param listener the hover listener
+   * @return this
+   * @see net.infonode.gui.hover.HoverEvent
+   */
+  public ViewTitleBarProperties setHoverListener(HoverListener listener) {
+    HOVER_LISTENER.set(getMap(), listener);
+    return this;
+  }
+
+  /**
+   * <p>Gets the hover listener that will be triggered when the title bar is hovered by the mouse.</p>
+   *
+   * <p>The view that contains the title bar will be the source of the hover event sent to the
+   * hover listener.</p>
+   *
+   * @return the hover listener
+   * @see net.infonode.gui.hover.HoverEvent
+   */
+  public HoverListener getHoverListener() {
+    return HOVER_LISTENER.get(getMap());
+  }
+}
diff --git a/src/net/infonode/docking/properties/ViewTitleBarStateProperties.java b/src/net/infonode/docking/properties/ViewTitleBarStateProperties.java
new file mode 100644
index 0000000..224889b
--- /dev/null
+++ b/src/net/infonode/docking/properties/ViewTitleBarStateProperties.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ViewTitleBarStateProperties.java,v 1.6 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.*;
+import net.infonode.util.Alignment;
+
+import javax.swing.*;
+
+/**
+ * Properties and property values for a view title bar state.
+ *
+ * @author johan
+ * @version $Revision: 1.6 $
+ * @since IDW 1.4.0
+ */
+public class ViewTitleBarStateProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all view title bar state properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("View Title Bar State Properties", "");
+
+  /**
+   * Properties for the component
+   *
+   * @see #getComponentProperties
+   */
+  public static final PropertyMapProperty COMPONENT_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                         "Component Properties",
+                                                                                         "Properties for title bar.",
+                                                                                         ComponentProperties.PROPERTIES);
+
+  /**
+   * Properties for the shaped panel
+   *
+   * @see #getShapedPanelProperties
+   */
+  public static final PropertyMapProperty SHAPED_PANEL_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                            "Shaped Panel Properties",
+                                                                                            "Properties for shaped title bar.",
+                                                                                            ShapedPanelProperties.PROPERTIES);
+  /**
+   * The minimize button property values.
+   *
+   * @see #getMinimizeButtonProperties
+   */
+  public static final PropertyMapProperty MINIMIZE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                               "Minimize Button Properties",
+                                                                                               "The minimize button property values.",
+                                                                                               WindowTabButtonProperties.PROPERTIES);
+
+
+  /**
+   * The minimize button property values.
+   *
+   * @see #getMinimizeButtonProperties
+   */
+  public static final PropertyMapProperty MAXIMIZE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                               "Maximize Button Properties",
+                                                                                               "The maximizee button property values.",
+                                                                                               WindowTabButtonProperties.PROPERTIES);
+  /**
+   * The restore button property values.
+   *
+   * @see #getRestoreButtonProperties
+   */
+  public static final PropertyMapProperty RESTORE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                              "Restore Button Properties",
+                                                                                              "The restore button property values.",
+                                                                                              WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The close button property values.
+   *
+   * @see #getCloseButtonProperties
+   */
+  public static final PropertyMapProperty CLOSE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                            "Close Button Properties",
+                                                                                            "The close button property values.",
+                                                                                            WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The undock button property values.
+   *
+   * @see #getUndockButtonProperties
+   */
+  public static final PropertyMapProperty UNDOCK_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                             "Undock Button Properties",
+                                                                                             "The undock button property values.",
+                                                                                             WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The dock button property values.
+   *
+   * @see #getDockButtonProperties
+   */
+  public static final PropertyMapProperty DOCK_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                           "Dock Button Properties",
+                                                                                           "The dockbutton property values.",
+                                                                                           WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The title bar title.
+   */
+  public static final StringProperty TITLE = new StringProperty(PROPERTIES,
+                                                                "Title",
+                                                                "The title bar title.",
+                                                                PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Title visible property
+   *
+   * @see #setTitleVisible
+   * @see #getTitleVisible
+   */
+  public static final BooleanProperty TITLE_VISIBLE = new BooleanProperty(PROPERTIES,
+                                                                          "Title Visible",
+                                                                          "Controls if the title should be visible or not.",
+                                                                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The title bar icon.
+   */
+  public static final IconProperty ICON = new IconProperty(PROPERTIES,
+                                                           "Icon",
+                                                           "The title bar icon.",
+                                                           PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Icon visible property
+   *
+   * @see #setIconVisible
+   * @see #getIconVisible
+   */
+  public static final BooleanProperty ICON_VISIBLE = new BooleanProperty(PROPERTIES,
+                                                                         "Icon Visible",
+                                                                         "Controls if the icon should be visible or not.",
+                                                                         PropertyMapValueHandler.INSTANCE);
+  /**
+   * Icon text gap property
+   *
+   * @see #setIconTextGap
+   * @see #getIconTextGap
+   */
+  public static final IntegerProperty ICON_TEXT_GAP = IntegerProperty.createPositive(PROPERTIES,
+                                                                                     "Icon Text Gap",
+                                                                                     "Gap in pixels between the icon and the title",
+                                                                                     2,
+                                                                                     PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Icon Text Horizontal alignment property
+   *
+   * @see #setIconTextHorizontalAlignment
+   * @see #getIconTextHorizontalAlignment
+   */
+  public static final AlignmentProperty ICON_TEXT_HORIZONTAL_ALIGNMENT = new AlignmentProperty(PROPERTIES,
+                                                                                               "Icon Text Horizontal Alignment",
+                                                                                               "Horizontal alignment for the icon and title text.",
+                                                                                               PropertyMapValueHandler.INSTANCE,
+                                                                                               Alignment.getHorizontalAlignments());
+
+  /**
+   * Button spacing
+   *
+   * @see #setButtonSpacing
+   * @see #getButtonSpacing
+   */
+  public static final IntegerProperty BUTTON_SPACING = IntegerProperty.createPositive(PROPERTIES,
+                                                                                      "Button Spacing",
+                                                                                      "Spacing in pixels between the buttons on the title bar",
+                                                                                      2,
+                                                                                      PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Creates an empty property object.
+   */
+  public ViewTitleBarStateProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property object containing the map.
+   *
+   * @param map the property map
+   */
+  public ViewTitleBarStateProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public ViewTitleBarStateProperties(ViewTitleBarStateProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public ViewTitleBarStateProperties addSuperObject(ViewTitleBarStateProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   */
+  public ViewTitleBarStateProperties removeSuperObject(ViewTitleBarStateProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Gets the component properties
+   *
+   * @return component properties
+   */
+  public ComponentProperties getComponentProperties() {
+    return new ComponentProperties(COMPONENT_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the shaped panel properties
+   *
+   * @return shaped panel properties
+   */
+  public ShapedPanelProperties getShapedPanelProperties() {
+    return new ShapedPanelProperties(SHAPED_PANEL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the minimize button property values.
+   *
+   * @return the minimize button property values
+   */
+  public WindowTabButtonProperties getMinimizeButtonProperties() {
+    return new WindowTabButtonProperties(MINIMIZE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the maximize button property values.
+   *
+   * @return the maximize button property values
+   */
+  public WindowTabButtonProperties getMaximizeButtonProperties() {
+    return new WindowTabButtonProperties(MAXIMIZE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the restore button property values.
+   *
+   * @return the restore button property values
+   */
+  public WindowTabButtonProperties getRestoreButtonProperties() {
+    return new WindowTabButtonProperties(RESTORE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the close button property values.
+   *
+   * @return the close button property values
+   */
+  public WindowTabButtonProperties getCloseButtonProperties() {
+    return new WindowTabButtonProperties(CLOSE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the undock button property values.
+   *
+   * @return the undock button property values
+   */
+  public WindowTabButtonProperties getUndockButtonProperties() {
+    return new WindowTabButtonProperties(UNDOCK_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the dock button property values.
+   *
+   * @return the dock button property values
+   */
+  public WindowTabButtonProperties getDockButtonProperties() {
+    return new WindowTabButtonProperties(DOCK_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Sets the spacing between the buttons on the title bar
+   *
+   * @param spacing spacing in pixels
+   * @return this
+   */
+  public ViewTitleBarStateProperties setButtonSpacing(int spacing) {
+    BUTTON_SPACING.set(getMap(), spacing);
+    return this;
+  }
+
+  /**
+   * Returns the spacing between the buttons on the title bar
+   *
+   * @return spacing in pixels
+   */
+  public int getButtonSpacing() {
+    return BUTTON_SPACING.get(getMap());
+  }
+
+
+  /**
+   * Sets the title.
+   *
+   * @param title the title
+   * @return this
+   */
+  public ViewTitleBarStateProperties setTitle(String title) {
+    TITLE.set(getMap(), title);
+
+    return this;
+  }
+
+  /**
+   * Returns the view title.
+   *
+   * @return the view title
+   */
+  public String getTitle() {
+    return TITLE.get(getMap());
+  }
+
+  /**
+   * Sets if the title should be visible or not
+   *
+   * @param visible True for visible, otherwise false
+   * @return this
+   */
+  public ViewTitleBarStateProperties setTitleVisible(boolean visible) {
+    TITLE_VISIBLE.set(getMap(), visible);
+    return this;
+  }
+
+  /**
+   * Returns if the title should be visible or not
+   *
+   * @return True if visible, otherwise false
+   */
+  public boolean getTitleVisible() {
+    return TITLE_VISIBLE.get(getMap());
+  }
+
+  /**
+   * Sets the icon.
+   *
+   * @param icon the icon
+   * @return this
+   */
+  public ViewTitleBarStateProperties setIcon(Icon icon) {
+    ICON.set(getMap(), icon);
+
+    return this;
+  }
+
+  /**
+   * Returns the view icon.
+   *
+   * @return the view icon
+   */
+  public Icon getIcon() {
+    return ICON.get(getMap());
+  }
+
+  /**
+   * Sets if the icon should be visible or not
+   *
+   * @param visible True for visible, otherwise false
+   * @return this
+   */
+  public ViewTitleBarStateProperties setIconVisible(boolean visible) {
+    ICON_VISIBLE.set(getMap(), visible);
+    return this;
+  }
+
+  /**
+   * Returns if the icon should be visible or not
+   *
+   * @return True if visible, otherwise false
+   */
+  public boolean getIconVisible() {
+    return ICON_VISIBLE.get(getMap());
+  }
+
+  /**
+   * Sets the gap between the icon and the title in the title bar
+   *
+   * @param gap gap in pixels
+   * @return this
+   */
+  public ViewTitleBarStateProperties setIconTextGap(int gap) {
+    ICON_TEXT_GAP.set(getMap(), gap);
+    return this;
+  }
+
+  /**
+   * Returns the gap between the icon and the title in the title bar
+   *
+   * @return gap in pixels
+   */
+  public int getIconTextGap() {
+    return ICON_TEXT_GAP.get(getMap());
+  }
+
+  /**
+   * Sets the text's and icon's horizontal alignment
+   *
+   * @param alignment text and icon alignment
+   * @return this
+   */
+  public ViewTitleBarStateProperties setIconTextHorizontalAlignment(Alignment alignment) {
+    ICON_TEXT_HORIZONTAL_ALIGNMENT.set(getMap(), alignment);
+    return this;
+  }
+
+  /**
+   * Gets the text's and icon's horizontal alignment
+   *
+   * @return text and icon alignment
+   */
+  public Alignment getIconTextHorizontalAlignment() {
+    return ICON_TEXT_HORIZONTAL_ALIGNMENT.get(getMap());
+  }
+}
diff --git a/src/net/infonode/docking/properties/WindowBarProperties.java b/src/net/infonode/docking/properties/WindowBarProperties.java
new file mode 100644
index 0000000..d8172ec
--- /dev/null
+++ b/src/net/infonode/docking/properties/WindowBarProperties.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowBarProperties.java,v 1.33 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+import net.infonode.properties.types.ColorProperty;
+import net.infonode.properties.types.IntegerProperty;
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * Properties and property values for window bars.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.33 $
+ */
+public class WindowBarProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all window bar properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Window Bar Properties", "");
+
+  /**
+   * The window bar component property values.
+   */
+  public static final PropertyMapProperty COMPONENT_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                         "Component Properties",
+                                                                                         "The WindowBar component properties.",
+                                                                                         ComponentProperties.PROPERTIES);
+
+  /**
+   * Inside this distance from the content panel edge the user can resize the content panel.
+   */
+  public static final IntegerProperty CONTENT_PANEL_EDGE_RESIZE_DISTANCE =
+      IntegerProperty.createPositive(PROPERTIES,
+                                     "Content Panel Edge Resize Distance",
+                                     "Inside this distance from the content panel edge the user can resize the content panel.",
+                                     2,
+                                     PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The minimum width of the window bar. If greater than 0, the window bar will always be visible and the user can drag
+   * windows to it.
+   */
+  public static final IntegerProperty MINIMUM_WIDTH =
+      IntegerProperty.createPositive(PROPERTIES,
+                                     "Minimum Width",
+                                     "The minimum width of the window bar. If greater than 0, the window bar will " +
+                                     "always be visible and the user can drag windows to it.",
+                                     2,
+                                     PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * When enabled causes the windows to change size continuously while dragging the split window divider.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final BooleanProperty CONTINUOUS_LAYOUT_ENABLED =
+      new BooleanProperty(PROPERTIES,
+                          "Continuous Layout Enabled",
+                          "When enabled causes the selected tab's content to change size continuously while resizing it.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The drag indicator color.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final ColorProperty DRAG_INDICATOR_COLOR =
+      new ColorProperty(PROPERTIES,
+                        "Drag Indicator Color",
+                        "The color for the resizer's drag indicator that is shown when continuous layout is disabled.",
+                        PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Properties for the tab window used by this window bar.
+   */
+  public static final PropertyMapProperty TAB_WINDOW_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES, "Tab Window Properties", "", TabWindowProperties.PROPERTIES);
+
+  private static WindowBarProperties[] DEFAULT_VALUES = new WindowBarProperties[4];
+
+  static {
+    final Direction[] directions = Direction.getDirections();
+
+    for (int i = 0; i < directions.length; i++) {
+      Direction dir = directions[i];
+      WindowBarProperties properties = new WindowBarProperties();
+      properties.getTabWindowProperties().getTabbedPanelProperties().setTabAreaOrientation(dir);
+      properties.getTabWindowProperties().getTabProperties().getTitledTabProperties().
+          getNormalProperties().setDirection(dir.isHorizontal() ? Direction.DOWN : Direction.RIGHT);
+/*      properties.getTabWindowProperties().getTabbedPanelProperties().getContentPanelProperties().
+          getComponentProperties().setInsets(new Insets(dir == Direction.DOWN ? 40 : 0,
+                                                        dir == Direction.RIGHT ? 40 : 0,
+                                                        dir == Direction.UP ? 40 : 0,
+                                                        dir == Direction. LEFT ? 40 : 0));*/
+      DEFAULT_VALUES[dir.getValue()] = properties;
+    }
+  }
+
+  /**
+   * Creates a property object which inherits the default property values.
+   *
+   * @param location the location of the window bar
+   * @return a property object which inherits the default property values
+   */
+  public static WindowBarProperties createDefault(Direction location) {
+    return new WindowBarProperties(DEFAULT_VALUES[location.getValue()]);
+  }
+
+  /**
+   * Creates an empty property object.
+   */
+  public WindowBarProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property object containing the map.
+   *
+   * @param map the property map
+   */
+  public WindowBarProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public WindowBarProperties(WindowBarProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public WindowBarProperties addSuperObject(WindowBarProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   * @since IDW 1.1.0
+   * @deprecated Use {@link #removeSuperObject(WindowBarProperties)} instead.
+   */
+  public WindowBarProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public WindowBarProperties removeSuperObject(WindowBarProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Returns the distance from the content panel edge which inside the user can resize the content panel.
+   *
+   * @return the distance from the content panel edge which inside the user can resize the content panel
+   */
+  public int getContentPanelEdgeResizeDistance() {
+    return CONTENT_PANEL_EDGE_RESIZE_DISTANCE.get(getMap());
+  }
+
+  /**
+   * Sets the distance from the content panel edge which inside the user can resize the content panel.
+   *
+   * @param width the distance from the content panel edge which inside the user can resize the content panel
+   * @return this
+   */
+  public WindowBarProperties setContentPanelEdgeResizeEdgeDistance(int width) {
+    CONTENT_PANEL_EDGE_RESIZE_DISTANCE.set(getMap(), width);
+    return this;
+  }
+
+  /**
+   * Returns the minimum width of the window bar.
+   *
+   * @return the minimum width of the window bar
+   */
+  public int getMinimumWidth() {
+    return MINIMUM_WIDTH.get(getMap());
+  }
+
+  /**
+   * Sets the minimum width of the window bar.
+   *
+   * @param width the minimum width of the window bar
+   * @return this
+   */
+  public WindowBarProperties setMinimumWidth(int width) {
+    MINIMUM_WIDTH.set(getMap(), width);
+    return this;
+  }
+
+  /**
+   * Returns the tab window property values.
+   *
+   * @return the tab window property values
+   */
+  public TabWindowProperties getTabWindowProperties() {
+    return new TabWindowProperties(TAB_WINDOW_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the property values for the window bar component.
+   *
+   * @return the property values for the window bar component
+   */
+  public ComponentProperties getComponentProperties() {
+    return new ComponentProperties(COMPONENT_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Sets the resizer's drag indicator color.
+   *
+   * @param color the color for the drag indicator
+   * @return this
+   * @since IDW 1.4.0
+   */
+  public WindowBarProperties setDragIndicatorColor(Color color) {
+    DRAG_INDICATOR_COLOR.set(getMap(), color);
+    return this;
+  }
+
+  /**
+   * Returns the resizer's drag indicator color.
+   *
+   * @return the drag indicator color
+   * @since IDW 1.4.0
+   */
+  public Color getDragIndicatorColor() {
+    return DRAG_INDICATOR_COLOR.get(getMap());
+  }
+
+  /**
+   * Returns true if continuous layout is enabled.
+   *
+   * @return true if continuous layout is enabled
+   * @since IDW 1.4.0
+   */
+  public boolean getContinuousLayoutEnabled() {
+    return CONTINUOUS_LAYOUT_ENABLED.get(getMap());
+  }
+
+  /**
+   * Enables/disables continuous layout.
+   *
+   * @param enabled if true continuous layout is enabled
+   * @return this
+   * @since IDW 1.4.0
+   */
+  public WindowBarProperties setContinuousLayoutEnabled(boolean enabled) {
+    CONTINUOUS_LAYOUT_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+}
diff --git a/src/net/infonode/docking/properties/WindowTabButtonProperties.java b/src/net/infonode/docking/properties/WindowTabButtonProperties.java
new file mode 100644
index 0000000..c23ee14
--- /dev/null
+++ b/src/net/infonode/docking/properties/WindowTabButtonProperties.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowTabButtonProperties.java,v 1.16 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.docking.action.DockingWindowAction;
+import net.infonode.docking.action.DockingWindowActionProperty;
+import net.infonode.gui.button.ButtonFactory;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+import net.infonode.properties.types.ButtonFactoryProperty;
+import net.infonode.properties.types.IconProperty;
+import net.infonode.properties.types.StringProperty;
+
+import javax.swing.*;
+
+/**
+ * Properties and property values for a button in a window tab.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.16 $
+ */
+public class WindowTabButtonProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all window tab button properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Window Tab Button Properties", "");
+
+  /**
+   * True if the button is visible.
+   */
+  public static final BooleanProperty VISIBLE = new BooleanProperty(PROPERTIES,
+                                                                    "Visible",
+                                                                    "True if the button is visible.",
+                                                                    PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The button icon.
+   */
+  public static final IconProperty ICON = new IconProperty(PROPERTIES,
+                                                           "Icon",
+                                                           "The button icon.",
+                                                           PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The button tool tip text.
+   *
+   * @since IDW 1.1.0
+   */
+  public static final StringProperty TOOL_TIP_TEXT = new StringProperty(PROPERTIES,
+                                                                        "Tool Tip Text",
+                                                                        "The button tool tip text.",
+                                                                        PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The {@link DockingWindowAction} that is performed when the button is clicked.
+   *
+   * @since IDW 1.3.0
+   */
+  public static final DockingWindowActionProperty ACTION =
+      new DockingWindowActionProperty(PROPERTIES,
+                                      "Action",
+                                      "The action that is performed when the button is clicked.",
+                                      PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The button factory.
+   * This factory is used to create the button when it's first needed. Modifying this property will NOT cause already
+   * created buttons to be replaced. The created button will be set to non-focusable and will be assigned the icon from
+   * {@link #ICON} and the tool tip from {@link #TOOL_TIP_TEXT}. An action listener is also added to the button.
+   *
+   * @since IDW 1.1.0
+   */
+  public static final ButtonFactoryProperty FACTORY =
+      new ButtonFactoryProperty(PROPERTIES,
+                                "Factory",
+                                "The button factory. This factory is used to create the button when it's first needed. " +
+                                "Modifying this property will NOT cause already created buttons to be replaced. The " +
+                                "created button will be set to non-focusable and will be assigned the icon from the '" +
+                                ICON.getName() + "' property and the tool tip from the '" + TOOL_TIP_TEXT.getName() +
+                                "' " +
+                                "property. An action listener that performs the action in set in the '" +
+                                ACTION.getName() +
+                                "' property is added to the button.",
+                                PropertyMapValueHandler.INSTANCE);
+
+
+  /**
+   * Creates an empty property object.
+   */
+  public WindowTabButtonProperties() {
+    super(PROPERTIES);
+  }
+
+  /**
+   * Creates a property object containing the map.
+   *
+   * @param map the property map
+   */
+  public WindowTabButtonProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public WindowTabButtonProperties(WindowTabButtonProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public WindowTabButtonProperties addSuperObject(WindowTabButtonProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   * @since IDW 1.1.0
+   * @deprecated Use {@link #removeSuperObject(WindowTabButtonProperties)} instead.
+   */
+  public WindowTabButtonProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public WindowTabButtonProperties removeSuperObject(WindowTabButtonProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Set to true if this button should be visible.
+   *
+   * @param visible true if this button should be visible
+   * @return this
+   */
+  public WindowTabButtonProperties setVisible(boolean visible) {
+    VISIBLE.set(getMap(), visible);
+    return this;
+  }
+
+  /**
+   * Returns true if this button is visible.
+   *
+   * @return true if this button is visible
+   */
+  public boolean isVisible() {
+    return VISIBLE.get(getMap());
+  }
+
+  /**
+   * Sets the button icon.
+   *
+   * @param icon the button icon
+   * @return this
+   */
+  public WindowTabButtonProperties setIcon(Icon icon) {
+    ICON.set(getMap(), icon);
+    return this;
+  }
+
+  /**
+   * Returns the button icon.
+   *
+   * @return the button icon
+   */
+  public Icon getIcon() {
+    return ICON.get(getMap());
+  }
+
+  /**
+   * Returns the button tool tip text.
+   *
+   * @return the button tool tip text
+   * @since IDW 1.1.0
+   */
+  public String getToolTipText() {
+    return TOOL_TIP_TEXT.get(getMap());
+  }
+
+  /**
+   * Sets the button tool tip text.
+   *
+   * @param text the button tool tip text
+   * @return this
+   * @since IDW 1.1.0
+   */
+  public WindowTabButtonProperties setToolTipText(String text) {
+    TOOL_TIP_TEXT.set(getMap(), text);
+    return this;
+  }
+
+  /**
+   * Gets the button factory.
+   * This factory is used to create the button when it's first needed. Modifying this property will NOT cause already
+   * created buttons to be replaced. The created button will be set to non-focusable and will be assigned the icon from
+   * {@link #ICON} and the tool tip from {@link #TOOL_TIP_TEXT}. An action listener is also added to the button.
+   *
+   * @return the button factory
+   * @since IDW 1.1.0
+   */
+  public ButtonFactory getFactory() {
+    return FACTORY.get(getMap());
+  }
+
+  /**
+   * Sets the button factory.
+   * This factory is used to create the button when it's first needed. Modifying this property will NOT cause already
+   * created buttons to be replaced. The created button will be set to non-focusable and will be assigned the icon from
+   * {@link #ICON} and the tool tip from {@link #TOOL_TIP_TEXT}. An action listener is also added to the button.
+   *
+   * @param factory the button factory
+   * @return this
+   * @since IDW 1.1.0
+   */
+  public WindowTabButtonProperties setFactory(ButtonFactory factory) {
+    FACTORY.set(getMap(), factory);
+    return this;
+  }
+
+  /**
+   * Gets the {@link DockingWindowAction} that is performed when the button is clicked.
+   *
+   * @return the {@link DockingWindowAction} that is performed when the button is clicked
+   * @since IDW 1.3.0
+   */
+  public DockingWindowAction getAction() {
+    return ACTION.get(getMap());
+  }
+
+  /**
+   * Sets the {@link DockingWindowAction} that will be performed when the button is clicked.
+   *
+   * @param action the {@link DockingWindowAction} that is performed when the button is clicked
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public WindowTabButtonProperties setAction(DockingWindowAction action) {
+    ACTION.set(getMap(), action);
+    return this;
+  }
+
+  /**
+   * Sets the action is performed when the button is clicked.
+   * Also sets the icon and tooltip text of the button using the values from
+   * {@link DockingWindowAction}.
+   *
+   * @param action the action that is performed when the button is clicked
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public WindowTabButtonProperties setTo(DockingWindowAction action) {
+    setAction(action);
+    setIcon(action.getIcon());
+    setToolTipText(action.getName());
+    return this;
+  }
+
+}
diff --git a/src/net/infonode/docking/properties/WindowTabProperties.java b/src/net/infonode/docking/properties/WindowTabProperties.java
new file mode 100644
index 0000000..15ba49f
--- /dev/null
+++ b/src/net/infonode/docking/properties/WindowTabProperties.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowTabProperties.java,v 1.21 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.properties.propertymap.*;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+import net.infonode.tabbedpanel.titledtab.TitledTabStateProperties;
+
+/**
+ * Properties and property values for window tabs.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.21 $
+ */
+public class WindowTabProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all window tab properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Tab Properties", "");
+
+  /**
+   * Property values for the titled tab used in the tab.
+   */
+  public static final PropertyMapProperty TITLED_TAB_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Titled Tab Properties",
+                              "Property values for the TitledTab used in the window tab.",
+                              TitledTabProperties.PROPERTIES);
+
+  /**
+   * Property values for the titled tab when it is focused or a component in the tab's content component has focus.
+   */
+  public static final PropertyMapProperty FOCUSED_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Focused Properties",
+                              "Property values for the TitledTab when the window is focused or a component in the tab's content component has focus.\n" +
+                              "The " + TITLED_TAB_PROPERTIES + '.' +
+                              TitledTabProperties.HIGHLIGHTED_PROPERTIES + " property values are inherited from.",
+                              TitledTabStateProperties.PROPERTIES);
+
+  /**
+   * Property values for the tab buttons when the tab is in the normal state.
+   */
+  public static final PropertyMapProperty NORMAL_BUTTON_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Normal Button Properties",
+                              "Property values for the tab buttons when the tab is in the normal state.",
+                              WindowTabStateProperties.PROPERTIES);
+
+  /**
+   * Property values for the tab buttons when the tab is highlighted.
+   */
+  public static final PropertyMapProperty HIGHLIGHTED_BUTTON_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Highlighted Button Properties",
+                              "Property values for the tab buttons when the tab is highlighted.",
+                              WindowTabStateProperties.PROPERTIES);
+
+  /**
+   * Property values for the tab buttons when the tab is focused or a component in the tab's content component has focus.
+   */
+  public static final PropertyMapProperty FOCUSED_BUTTON_PROPERTIES =
+      new PropertyMapProperty(PROPERTIES,
+                              "Focused Button Properties",
+                              "Property values for the tab buttons when the tab is focused or a component in the tab's content component has focus.",
+                              WindowTabStateProperties.PROPERTIES);
+
+  /**
+   * Creates an empty property object.
+   */
+  public WindowTabProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Creates a property object containing the map.
+   *
+   * @param map the property map
+   */
+  public WindowTabProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public WindowTabProperties(WindowTabProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public WindowTabProperties addSuperObject(WindowTabProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   * @since IDW 1.1.0
+   * @deprecated Use {@link #removeSuperObject(WindowTabProperties)} instead.
+   */
+  public WindowTabProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public WindowTabProperties removeSuperObject(WindowTabProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Returns the property values for the titled tab used in the tab.
+   *
+   * @return the property values for the titled tab used in the tab
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return new TitledTabProperties(TITLED_TAB_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the property values for the titled tab when it is focused or a component in the tab's content component has focus.
+   *
+   * @return the property values for the titled tab when it is focused or a component in the tab's content component has focus
+   */
+  public TitledTabStateProperties getFocusedProperties() {
+    return new TitledTabStateProperties(FOCUSED_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the property values for the tab buttons when the tab is in the normal state.
+   *
+   * @return the property values for the tab buttons when the tab is in the normal state
+   */
+  public WindowTabStateProperties getNormalButtonProperties() {
+    return new WindowTabStateProperties(NORMAL_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the property values for the tab buttons when the tab is highlighted.
+   *
+   * @return the property values for the tab buttons when the tab is highlighted
+   */
+  public WindowTabStateProperties getHighlightedButtonProperties() {
+    return new WindowTabStateProperties(HIGHLIGHTED_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the property values for the tab buttons when the tab is focused or a component in the tab's content
+   * component has focus.
+   *
+   * @return the property values for the tab buttons when the tab is focused or a component in the tab's content
+   *         component has focus
+   */
+  public WindowTabStateProperties getFocusedButtonProperties() {
+    return new WindowTabStateProperties(FOCUSED_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+}
diff --git a/src/net/infonode/docking/properties/WindowTabStateProperties.java b/src/net/infonode/docking/properties/WindowTabStateProperties.java
new file mode 100644
index 0000000..af4b8cc
--- /dev/null
+++ b/src/net/infonode/docking/properties/WindowTabStateProperties.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowTabStateProperties.java,v 1.14 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.properties;
+
+import net.infonode.properties.propertymap.*;
+
+/**
+ * Properties and property values for the window tab buttons.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.14 $
+ */
+public class WindowTabStateProperties extends PropertyMapContainer {
+  /**
+   * Property group containing all window tab state properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("State Properties", "");
+
+  /**
+   * The minimize button property values.
+   */
+  public static final PropertyMapProperty MINIMIZE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                               "Minimize Button Properties",
+                                                                                               "The minimize button property values.",
+                                                                                               WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The restore button property values.
+   */
+  public static final PropertyMapProperty RESTORE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                              "Restore Button Properties",
+                                                                                              "The restore button property values.",
+                                                                                              WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The close button property values.
+   */
+  public static final PropertyMapProperty CLOSE_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                            "Close Button Properties",
+                                                                                            "The close button property values.",
+                                                                                            WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The undock button property values.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final PropertyMapProperty UNDOCK_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                             "Undock Button Properties",
+                                                                                             "The undock button property values.",
+                                                                                             WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * The dock button property values.
+   *
+   * @since IDW 1.4.0
+   */
+  public static final PropertyMapProperty DOCK_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                           "Dock Button Properties",
+                                                                                           "The dock button property values.",
+                                                                                           WindowTabButtonProperties.PROPERTIES);
+
+  /**
+   * Creates an empty property object.
+   */
+  public WindowTabStateProperties() {
+    super(PROPERTIES);
+  }
+
+  /**
+   * Creates a property object containing the map.
+   *
+   * @param map the property map
+   */
+  public WindowTabStateProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public WindowTabStateProperties(WindowTabStateProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public WindowTabStateProperties addSuperObject(WindowTabStateProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   * @since IDW 1.1.0
+   * @deprecated Use {@link #removeSuperObject(WindowTabStateProperties)} instead.
+   */
+  public WindowTabStateProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes a super object.
+   *
+   * @param superObject the super object to remove
+   * @return this
+   * @since IDW 1.3.0
+   */
+  public WindowTabStateProperties removeSuperObject(WindowTabStateProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Returns the minimize button property values.
+   *
+   * @return the minimize button property values
+   */
+  public WindowTabButtonProperties getMinimizeButtonProperties() {
+    return new WindowTabButtonProperties(MINIMIZE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the restore button property values.
+   *
+   * @return the restore button property values
+   */
+  public WindowTabButtonProperties getRestoreButtonProperties() {
+    return new WindowTabButtonProperties(RESTORE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the close button property values.
+   *
+   * @return the close button property values
+   */
+  public WindowTabButtonProperties getCloseButtonProperties() {
+    return new WindowTabButtonProperties(CLOSE_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the undock button property values.
+   *
+   * @return the undock button property values
+   * @since IDW 1.4.0
+   */
+  public WindowTabButtonProperties getUndockButtonProperties() {
+    return new WindowTabButtonProperties(UNDOCK_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Returns the dock button property values.
+   *
+   * @return the dock button property values
+   * @since IDW 1.4.0
+   */
+  public WindowTabButtonProperties getDockButtonProperties() {
+    return new WindowTabButtonProperties(DOCK_BUTTON_PROPERTIES.get(getMap()));
+  }
+}
diff --git a/src/net/infonode/docking/properties/package.html b/src/net/infonode/docking/properties/package.html
new file mode 100644
index 0000000..08adbfd
--- /dev/null
+++ b/src/net/infonode/docking/properties/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Property classes for docking windows.
+</body>
+</html>
diff --git a/src/net/infonode/docking/theme/BlueHighlightDockingTheme.java b/src/net/infonode/docking/theme/BlueHighlightDockingTheme.java
new file mode 100644
index 0000000..f49cabb
--- /dev/null
+++ b/src/net/infonode/docking/theme/BlueHighlightDockingTheme.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BlueHighlightDockingTheme.java,v 1.11 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.docking.theme;
+
+import net.infonode.docking.properties.RootWindowProperties;
+import net.infonode.docking.properties.WindowTabProperties;
+import net.infonode.gui.icon.button.CloseIcon;
+import net.infonode.gui.icon.button.MinimizeIcon;
+import net.infonode.gui.icon.button.RestoreIcon;
+import net.infonode.tabbedpanel.theme.BlueHighlightTheme;
+
+import java.awt.*;
+
+/**
+ * A theme where the tab of the focused window has a blue background.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.11 $
+ */
+public final class BlueHighlightDockingTheme extends DockingWindowsTheme {
+  private RootWindowProperties rootWindowProperties;
+
+  public BlueHighlightDockingTheme() {
+    rootWindowProperties = createRootWindowProperties();
+  }
+
+  public String getName() {
+    return "Blue Highlight Theme";
+  }
+
+  public RootWindowProperties getRootWindowProperties() {
+    return rootWindowProperties;
+  }
+
+  /**
+   * Create a root window properties object with the property values for this theme.
+   *
+   * @return the root window properties object
+   */
+  public static final RootWindowProperties createRootWindowProperties() {
+    BlueHighlightTheme blueHighlightTheme = new BlueHighlightTheme();
+    BlueHighlightTheme greyHighlightTheme = new BlueHighlightTheme(new Color(150, 150, 150));
+
+    RootWindowProperties properties = new RootWindowProperties();
+
+    properties.getTabWindowProperties().getTabbedPanelProperties().
+        addSuperObject(greyHighlightTheme.getTabbedPanelProperties());
+
+    WindowTabProperties tabProperties = properties.getTabWindowProperties().getTabProperties();
+    tabProperties.getTitledTabProperties().addSuperObject(greyHighlightTheme.getTitledTabProperties());
+
+//    tabProperties.getTitledTabProperties().getHighlightedProperties().setFont(UIManager.getFont("font"));
+
+/*    tabProperties.getFocusedProperties()
+        .setForegroundColor(Color.WHITE)
+        .setBackgroundColor(new Color(100, 130, 220));
+*/
+
+    tabProperties.getFocusedProperties().addSuperObject(
+        blueHighlightTheme.getTitledTabProperties().getHighlightedProperties());
+
+    tabProperties.getHighlightedButtonProperties().getCloseButtonProperties()
+        .setIcon(new CloseIcon(Color.WHITE, RootWindowProperties.DEFAULT_WINDOW_TAB_BUTTON_ICON_SIZE));
+
+    tabProperties.getHighlightedButtonProperties().getMinimizeButtonProperties()
+        .setIcon(new MinimizeIcon(Color.WHITE, RootWindowProperties.DEFAULT_WINDOW_TAB_BUTTON_ICON_SIZE));
+
+    tabProperties.getHighlightedButtonProperties().getRestoreButtonProperties()
+        .setIcon(new RestoreIcon(Color.WHITE, RootWindowProperties.DEFAULT_WINDOW_TAB_BUTTON_ICON_SIZE));
+
+    properties.getComponentProperties().setBackgroundColor(new Color(180, 190, 220));
+
+    return properties;
+  }
+
+}
diff --git a/src/net/infonode/docking/theme/ClassicDockingTheme.java b/src/net/infonode/docking/theme/ClassicDockingTheme.java
new file mode 100644
index 0000000..1eb9921
--- /dev/null
+++ b/src/net/infonode/docking/theme/ClassicDockingTheme.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ClassicDockingTheme.java,v 1.11 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.theme;
+
+import net.infonode.docking.properties.RootWindowProperties;
+import net.infonode.gui.border.EdgeBorder;
+import net.infonode.gui.colorprovider.UIManagerColorProvider;
+import net.infonode.tabbedpanel.TabSelectTrigger;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.TabbedUtils;
+import net.infonode.tabbedpanel.theme.ClassicTheme;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+import net.infonode.util.Direction;
+
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import java.awt.*;
+
+/**
+ * A theme with a "classic" look with round edges for the tabs.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.11 $
+ * @since IDW 1.2.0
+ */
+public class ClassicDockingTheme extends DockingWindowsTheme {
+  private RootWindowProperties rootWindowProperties = new RootWindowProperties();
+
+  /**
+   * Creates a ClassicDockingTheme.
+   */
+  public ClassicDockingTheme() {
+    ClassicTheme theme = new ClassicTheme();
+
+    TabbedPanelProperties tabbedPanelProperties = theme.getTabbedPanelProperties();
+    TitledTabProperties titledTabProperties = theme.getTitledTabProperties();
+
+    Border insetsBorder = new Border() {
+      public boolean isBorderOpaque() {
+        return false;
+      }
+
+      public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+      }
+
+      public Insets getBorderInsets(Component c) {
+        TabbedPanel tp = TabbedUtils.getParentTabbedPanel(c);
+        if (tp != null) {
+          Direction d = tp.getProperties().getTabAreaOrientation();
+          return new Insets(d == Direction.UP ? 2 : 0,
+                            d == Direction.LEFT ? 2 : 0,
+                            d == Direction.DOWN ? 2 : 0,
+                            d == Direction.RIGHT ? 2 : 0);
+        }
+        return new Insets(0, 0, 0, 0);
+      }
+    };
+
+    // Tab window
+    rootWindowProperties.getTabWindowProperties().getTabbedPanelProperties().setTabSelectTrigger(
+        TabSelectTrigger.MOUSE_PRESS)
+        .addSuperObject(tabbedPanelProperties)
+        .getTabAreaComponentsProperties().getComponentProperties().setInsets(new Insets(0, 0, 0, 0));
+    rootWindowProperties.getTabWindowProperties().getTabProperties().getTitledTabProperties().addSuperObject(
+        titledTabProperties);
+
+    // Window bar
+    rootWindowProperties.getWindowBarProperties().getTabWindowProperties().getTabbedPanelProperties().addSuperObject(
+        tabbedPanelProperties);
+    rootWindowProperties.getWindowBarProperties().getTabWindowProperties().getTabProperties().getTitledTabProperties()
+        .addSuperObject(titledTabProperties);
+
+    rootWindowProperties.getWindowBarProperties().getTabWindowProperties().getTabbedPanelProperties()
+        .getTabAreaComponentsProperties()
+        .getComponentProperties()
+        .setBorder(new CompoundBorder(insetsBorder, theme.createTabBorder(true, false, true)));
+
+    rootWindowProperties.getWindowBarProperties()
+        .getComponentProperties()
+        .setInsets(new Insets(0, 0, 2, 0));
+
+    rootWindowProperties.getWindowBarProperties()
+        .getTabWindowProperties()
+        .getTabProperties()
+        .getTitledTabProperties()
+        .getNormalProperties()
+        .getComponentProperties()
+        .setBorder(theme.createInsetsTabBorder(true, false, true));
+
+    // Tweak root window
+    rootWindowProperties.getWindowAreaProperties().setBackgroundColor(null).setInsets(new Insets(0, 0, 0, 0))
+        .setBorder(new EdgeBorder(UIManagerColorProvider.TABBED_PANE_DARK_SHADOW,
+                                  UIManagerColorProvider.TABBED_PANE_HIGHLIGHT,
+                                  true,
+                                  true,
+                                  true,
+                                  true));
+    rootWindowProperties.setDragRectangleBorderWidth(3);
+    rootWindowProperties.getViewProperties().getViewTitleBarProperties().getNormalProperties()
+        .getShapedPanelProperties()
+        .setDirection(Direction.DOWN);
+  }
+
+  /**
+   * Gets the theme name
+   *
+   * @return name
+   */
+  public String getName() {
+    return "Classic Theme";
+  }
+
+  /**
+   * Gets the theme RootWindowProperties
+   *
+   * @return the RootWindowProperties
+   */
+  public RootWindowProperties getRootWindowProperties() {
+    return rootWindowProperties;
+  }
+}
diff --git a/src/net/infonode/docking/theme/DefaultDockingTheme.java b/src/net/infonode/docking/theme/DefaultDockingTheme.java
new file mode 100644
index 0000000..7da6fb2
--- /dev/null
+++ b/src/net/infonode/docking/theme/DefaultDockingTheme.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DefaultDockingTheme.java,v 1.3 2004/09/28 14:48:03 jesper Exp $
+package net.infonode.docking.theme;
+
+import net.infonode.docking.properties.RootWindowProperties;
+
+/**
+ * A helper class that contains an empty theme. This class is only available to make it easier to apply themes to a
+ * {@link net.infonode.docking.RootWindow}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @since IDW 1.1.0
+ */
+public class DefaultDockingTheme extends DockingWindowsTheme {
+  private RootWindowProperties rootWindowProperties = new RootWindowProperties();
+
+  /**
+   * Constructor.
+   */
+  public DefaultDockingTheme() {
+  }
+
+  public String getName() {
+    return "Default Theme";
+  }
+
+  public RootWindowProperties getRootWindowProperties() {
+    return rootWindowProperties;
+  }
+}
diff --git a/src/net/infonode/docking/theme/DockingWindowsTheme.java b/src/net/infonode/docking/theme/DockingWindowsTheme.java
new file mode 100644
index 0000000..4c3a3a7
--- /dev/null
+++ b/src/net/infonode/docking/theme/DockingWindowsTheme.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowsTheme.java,v 1.3 2004/09/28 14:48:03 jesper Exp $
+package net.infonode.docking.theme;
+
+import net.infonode.docking.properties.RootWindowProperties;
+
+/**
+ * A docking windows theme. A theme provides a {@link RootWindowProperties} object which can be applied to a
+ * {@link net.infonode.docking.RootWindow} like this:
+ *
+ * <pre>
+ * rootWindow.getRootWindowProperties().addSuperObject(theme.getRootWindowProperties());
+ * </pre>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @since IDW 1.1.0
+ */
+abstract public class DockingWindowsTheme {
+  /**
+   * Returns the name of this theme.
+   *
+   * @return the name of this theme
+   */
+  abstract public String getName();
+
+  /**
+   * Returns the root window properties for this theme.
+   *
+   * @return the root window properties for this theme
+   */
+  abstract public RootWindowProperties getRootWindowProperties();
+
+  protected DockingWindowsTheme() {
+  }
+
+  public String toString() {
+    return getName();
+  }
+
+}
diff --git a/src/net/infonode/docking/theme/GradientDockingTheme.java b/src/net/infonode/docking/theme/GradientDockingTheme.java
new file mode 100644
index 0000000..629e35a
--- /dev/null
+++ b/src/net/infonode/docking/theme/GradientDockingTheme.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: GradientDockingTheme.java,v 1.12 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.docking.theme;
+
+import net.infonode.docking.properties.RootWindowProperties;
+import net.infonode.docking.properties.WindowBarProperties;
+import net.infonode.gui.colorprovider.ColorMultiplier;
+import net.infonode.gui.colorprovider.UIManagerColorProvider;
+import net.infonode.gui.componentpainter.GradientComponentPainter;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.tabbedpanel.border.OpenContentBorder;
+import net.infonode.tabbedpanel.border.TabAreaLineBorder;
+import net.infonode.tabbedpanel.theme.GradientTheme;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.LineBorder;
+import java.awt.*;
+
+/**
+ * A theme that draws gradient tab backgrounds.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.12 $
+ * @since IDW 1.1.0
+ */
+public class GradientDockingTheme extends DockingWindowsTheme {
+  private boolean opaqueTabArea;
+  private boolean shadowEnabled;
+  private boolean highlightedBold;
+  private boolean focusHighlighterEnabled;
+  private Color borderColor;
+  private Color tabAreaBackgroundColor;
+  private RootWindowProperties rootProperties;
+
+  /**
+   * Creates a default theme with opaque title bar, shadows and focus highlighter.
+   */
+  public GradientDockingTheme() {
+    this(true, true, false, true);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param opaqueTabArea           set to true if the tab area should be opaque
+   * @param shadowEnabled           shadow on/off
+   * @param highlightedBold         if true the highlighted tab text uses a bold font
+   * @param focusHighlighterEnabled if true the currently focused tab is highlighted
+   */
+  public GradientDockingTheme(boolean opaqueTabArea, boolean shadowEnabled, boolean highlightedBold,
+                              boolean focusHighlighterEnabled) {
+    this(opaqueTabArea, shadowEnabled, highlightedBold, focusHighlighterEnabled, Color.BLACK);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param opaqueTabArea           set to true if the tab area should be opaque
+   * @param shadowEnabled           shadow on/off
+   * @param highlightedBold         if true the highlighted tab text uses a bold font
+   * @param focusHighlighterEnabled if true the currently focused tab is highlighted
+   * @param borderColor             the border color
+   */
+  public GradientDockingTheme(boolean opaqueTabArea, boolean shadowEnabled, boolean highlightedBold,
+                              boolean focusHighlighterEnabled, Color borderColor) {
+    this(opaqueTabArea, shadowEnabled, highlightedBold, focusHighlighterEnabled, borderColor,
+         GradientTheme.DEFAULT_TAB_AREA_BACKGROUND_COLOR);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param opaqueTabArea           set to true if the tab area should be opaque
+   * @param shadowEnabled           shadow on/off
+   * @param highlightedBold         if true the highlighted tab text uses a bold font
+   * @param focusHighlighterEnabled if true the currently focused tab is highlighted
+   * @param borderColor             the border color
+   * @param tabAreaBackgroundColor  the background color for the tab area and tabs in the normal state
+   */
+  public GradientDockingTheme(boolean opaqueTabArea, boolean shadowEnabled, boolean highlightedBold,
+                              boolean focusHighlighterEnabled, Color borderColor, Color tabAreaBackgroundColor) {
+    this.opaqueTabArea = opaqueTabArea;
+    this.shadowEnabled = shadowEnabled;
+    this.highlightedBold = highlightedBold;
+    this.focusHighlighterEnabled = focusHighlighterEnabled;
+    this.borderColor = borderColor;
+    this.tabAreaBackgroundColor = tabAreaBackgroundColor;
+
+    GradientTheme theme = new GradientTheme(opaqueTabArea, shadowEnabled, borderColor);
+
+    rootProperties = new RootWindowProperties();
+    createRootWindowProperties(theme);
+    createWindowBarProperties(theme);
+  }
+
+  private void createRootWindowProperties(GradientTheme theme) {
+    rootProperties.getTabWindowProperties().getTabbedPanelProperties().addSuperObject(theme.getTabbedPanelProperties());
+    rootProperties.getTabWindowProperties().getTabProperties().getTitledTabProperties().addSuperObject(
+        theme.getTitledTabProperties());
+
+    rootProperties.getTabWindowProperties().getCloseButtonProperties().setVisible(false);
+
+    if (!shadowEnabled)
+      rootProperties.getWindowAreaProperties().setInsets(new Insets(6, 6, 6, 6));
+
+    rootProperties.getWindowAreaShapedPanelProperties().setComponentPainter(new GradientComponentPainter(
+        UIManagerColorProvider.DESKTOP_BACKGROUND,
+        new ColorMultiplier(UIManagerColorProvider.DESKTOP_BACKGROUND, 0.9f),
+        new ColorMultiplier(UIManagerColorProvider.DESKTOP_BACKGROUND, 0.9f),
+        new ColorMultiplier(UIManagerColorProvider.DESKTOP_BACKGROUND, 0.8f)));
+
+    rootProperties.getWindowAreaProperties().setBorder(new LineBorder(Color.BLACK));
+
+    if (tabAreaBackgroundColor != null)
+      rootProperties.getComponentProperties().setBackgroundColor(tabAreaBackgroundColor);
+
+    if (!shadowEnabled)
+      rootProperties.getSplitWindowProperties().setDividerSize(6);
+
+    TitledTabProperties tabProperties = rootProperties.getTabWindowProperties().getTabProperties()
+        .getTitledTabProperties();
+
+    tabProperties.getNormalProperties().setIconVisible(false);
+    tabProperties.getHighlightedProperties().setIconVisible(true);
+
+    if (!highlightedBold)
+      tabProperties.getHighlightedProperties().
+          getComponentProperties().getMap().
+          createRelativeRef(ComponentProperties.FONT,
+                            tabProperties.getNormalProperties().getComponentProperties().getMap(),
+                            ComponentProperties.FONT);
+
+    if (focusHighlighterEnabled) {
+      tabProperties.getHighlightedProperties().getComponentProperties()
+          .setBorder(new CompoundBorder(opaqueTabArea ?
+                                        (Border) new TabAreaLineBorder(false, false, true, true) :
+                                        new TabAreaLineBorder(borderColor),
+                                        theme.getTabAreaComponentsGradientBorder()));
+
+      rootProperties.getTabWindowProperties().getTabProperties().getFocusedProperties().getComponentProperties()
+          .setBorder(new CompoundBorder(opaqueTabArea ?
+                                        (Border) new TabAreaLineBorder(false, false, true, true) :
+                                        new TabAreaLineBorder(borderColor),
+                                        theme.getHighlightedTabGradientBorder()));
+    }
+
+    rootProperties.getTabWindowProperties().getTabbedPanelProperties().getTabAreaComponentsProperties()
+        .getComponentProperties().setInsets(opaqueTabArea ? new Insets(0, 3, 0, 3) : new Insets(1, 3, 1, 3));
+
+
+    rootProperties.getTabWindowProperties().getTabProperties().getHighlightedButtonProperties()
+        .getCloseButtonProperties()
+        .setVisible(false);
+
+    rootProperties.getTabWindowProperties().getTabProperties().getHighlightedButtonProperties()
+        .getMinimizeButtonProperties()
+        .setVisible(true);
+
+    rootProperties.getTabWindowProperties().getTabProperties().getHighlightedButtonProperties()
+        .getRestoreButtonProperties()
+        .setVisible(true);
+  }
+
+  private void createWindowBarProperties(GradientTheme theme) {
+    WindowBarProperties barProperties = rootProperties.getWindowBarProperties();
+
+    barProperties.getTabWindowProperties().getTabbedPanelProperties().getContentPanelProperties()
+        .getComponentProperties()
+        .setBorder(new OpenContentBorder(Color.BLACK, 1));
+
+    barProperties.getTabWindowProperties().getTabProperties().getNormalButtonProperties().
+        getCloseButtonProperties().setVisible(false);
+
+    barProperties.getTabWindowProperties().getTabProperties().getTitledTabProperties().getNormalProperties()
+        .setIconVisible(true)
+        .getComponentProperties()
+        .setBorder(new CompoundBorder(new TabAreaLineBorder(), theme.getTabAreaComponentsGradientBorder()));
+
+    barProperties.getTabWindowProperties().getTabProperties().getFocusedProperties()
+        .getComponentProperties()
+        .setBorder(new CompoundBorder(new TabAreaLineBorder(Color.BLACK), theme.getHighlightedTabGradientBorder()));
+
+    barProperties.getTabWindowProperties().getTabProperties().getTitledTabProperties().getHighlightedProperties()
+        .getComponentProperties()
+        .setBorder(new CompoundBorder(new TabAreaLineBorder(Color.BLACK), theme.getHighlightedTabGradientBorder()));
+
+    barProperties.getTabWindowProperties().getTabbedPanelProperties().setTabSpacing(-1);
+
+    barProperties.getTabWindowProperties().getTabbedPanelProperties().getTabAreaProperties().getComponentProperties()
+        .setBorder(null)
+        .setBackgroundColor(null);
+  }
+
+  public String getName() {
+    String str = (opaqueTabArea ? "" : "Transparent Tab Area, ") +
+                 (shadowEnabled ? "" : "No Shadow, ") +
+                 (focusHighlighterEnabled ? "" : "No Focus Highlight, ") +
+                 (highlightedBold ? "Highlighted Bold, " : "");
+    return "Gradient Theme" + (str.length() > 0 ? " - " + str.substring(0, str.length() - 2) : "");
+  }
+
+  public RootWindowProperties getRootWindowProperties() {
+    return rootProperties;
+  }
+
+}
diff --git a/src/net/infonode/docking/theme/LookAndFeelDockingTheme.java b/src/net/infonode/docking/theme/LookAndFeelDockingTheme.java
new file mode 100644
index 0000000..e894abd
--- /dev/null
+++ b/src/net/infonode/docking/theme/LookAndFeelDockingTheme.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: LookAndFeelDockingTheme.java,v 1.21 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.theme;
+
+import net.infonode.docking.properties.RootWindowProperties;
+import net.infonode.docking.properties.ViewTitleBarProperties;
+import net.infonode.docking.properties.WindowBarProperties;
+import net.infonode.docking.theme.internal.laftheme.TitleBarUI;
+import net.infonode.docking.theme.internal.laftheme.TitleBarUIListener;
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.colorprovider.BackgroundColorProvider;
+import net.infonode.gui.componentpainter.ComponentPainter;
+import net.infonode.gui.componentpainter.CompoundComponentPainter;
+import net.infonode.gui.componentpainter.SolidColorComponentPainter;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.PropertyMapManager;
+import net.infonode.tabbedpanel.TabbedUtils;
+import net.infonode.tabbedpanel.theme.LookAndFeelTheme;
+import net.infonode.tabbedpanel.titledtab.TitledTabSizePolicy;
+import net.infonode.util.Direction;
+
+import javax.swing.border.Border;
+import java.awt.*;
+
+/**
+ * <p>
+ * An <strong>experimental</strong> theme that tries to replicate the look of
+ * the active look and feel. This may or may not work depending on the look and
+ * feel used.
+ * </p>
+ *
+ * <p>
+ * This is a theme that tries to replicate the look using the active look and
+ * feel. The tab windows will resemble the JTabbedPane look and the view title
+ * bars will resemble the JInternalFrame's title bar. Note that vertical title
+ * bars might not look very nice. The theme uses heavyweight AWT components
+ * internally so the {@link #dispose()} method must be called when the theme is
+ * no longer needed, otherwise the native resources will not be disposed.
+ * </p>
+ *
+ * <p>
+ * The theme uses the hover mechanism so that tab hover effects can be
+ * replicated. Only title bars above or below the view's content are supported.
+ * </p>
+ *
+ * <p>
+ * <strong>This theme is considered to be experimental and is not guaranteed to
+ * be an exact replica of the active look and feel. It is also not guaranteed to
+ * work together with the active look and feel. The theme may be changed,
+ * removed etc in future versions. No support is given for the theme. This theme
+ * doesn't work well with Aqua Look and Feel on Macintosh.</strong>
+ * </p>
+ *
+ * @author johan
+ * @version $Revision: 1.21 $
+ * @see net.infonode.tabbedpanel.theme.LookAndFeelTheme
+ * @since IDW 1.4.0
+ */
+public class LookAndFeelDockingTheme extends DockingWindowsTheme {
+
+  private static LookAndFeelTheme tpTheme;
+
+  private static RootWindowProperties rootProps = new RootWindowProperties();
+
+  private RootWindowProperties themeRootProps = new RootWindowProperties();
+
+  private static TitleBarUI titleBarUI;
+
+  private static int themeCounter = 0;
+
+  private boolean disposed = false;
+
+  public LookAndFeelDockingTheme() {
+    if (themeCounter == 0) {
+      tpTheme = new LookAndFeelTheme();
+
+      titleBarUI = new TitleBarUI(new TitleBarUIListener() {
+        public void updating() {
+        }
+
+        public void updated() {
+          initTheme(false);
+        }
+      }, false);
+
+      titleBarUI.init();
+
+      initTheme(true);
+    }
+
+    themeCounter++;
+
+    themeRootProps.addSuperObject(rootProps);
+  }
+
+  /**
+   * Gets the theme name
+   *
+   * @return name
+   */
+  public String getName() {
+    return "Look and Feel Theme";
+  }
+
+  /**
+   * Gets the theme RootWindowProperties
+   *
+   * @return the RootWindowProperties
+   */
+  public RootWindowProperties getRootWindowProperties() {
+    return themeRootProps;
+  }
+
+  /**
+   * <p>
+   * Disposes this theme.
+   * </p>
+   *
+   * <p>
+   * This method must be called in order to dispose the heavyweight AWT
+   * components used internally.
+   * </p>
+   */
+  public void dispose() {
+    if (!disposed) {
+      disposed = true;
+
+      themeCounter--;
+
+      if (themeCounter == 0) {
+        titleBarUI.dispose();
+
+        PropertyMapManager.runBatch(new Runnable() {
+          public void run() {
+            rootProps.getTabWindowProperties().getTabbedPanelProperties().removeSuperObject(
+                tpTheme.getTabbedPanelProperties());
+            rootProps.getTabWindowProperties().getTabProperties().getTitledTabProperties().removeSuperObject(
+                tpTheme.getTitledTabProperties());
+            rootProps.getMap().clear(true);
+          }
+        });
+
+        tpTheme.dispose();
+      }
+    }
+  }
+
+  private void initTheme(final boolean initial) {
+    PropertyMapManager.runBatch(new Runnable() {
+      public void run() {
+        if (initial) {
+          rootProps.getTabWindowProperties().getTabbedPanelProperties().addSuperObject(
+              tpTheme.getTabbedPanelProperties());
+          rootProps.getTabWindowProperties().getTabProperties().getTitledTabProperties().addSuperObject(
+              tpTheme.getTitledTabProperties());
+        }
+
+        rootProps.getMap().clear(true);
+
+        {
+          // Window bar
+          WindowBarProperties barProps = rootProps.getWindowBarProperties();
+          // barProps.setContinuousLayoutEnabled(false);
+          barProps.getTabWindowProperties().getTabProperties().getTitledTabProperties().setSizePolicy(
+              TitledTabSizePolicy.EQUAL_SIZE);
+          barProps.getComponentProperties().setBorder(null);
+
+          final Border contentBorder = tpTheme.getTabbedPanelProperties().getContentPanelProperties()
+              .getComponentProperties()
+              .getBorder();
+
+          ComponentPainter barContentPainter = tpTheme.getTabbedPanelProperties().getContentPanelProperties()
+              .getShapedPanelProperties()
+              .getComponentPainter();
+          barProps.getTabWindowProperties().getTabbedPanelProperties().getContentPanelProperties()
+              .getShapedPanelProperties()
+              .setOpaque(true).setComponentPainter(
+              new CompoundComponentPainter(new SolidColorComponentPainter(BackgroundColorProvider.INSTANCE),
+                                           barContentPainter));
+
+          barProps.getTabWindowProperties().getTabbedPanelProperties().getTabAreaComponentsProperties()
+              .getComponentProperties()
+              .setBorder(null);
+          barProps.getTabWindowProperties().getTabbedPanelProperties().getContentPanelProperties()
+              .getComponentProperties()
+              .setBorder(new Border() {
+                public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+                }
+
+                public Insets getBorderInsets(Component c) {
+                  Insets insets = (Insets) contentBorder.getBorderInsets(c).clone();
+
+                  Direction areaOrientation = TabbedUtils.getParentTabbedPanelContentPanel(c).getTabbedPanel()
+                      .getProperties()
+                      .getTabAreaOrientation();
+                  int minResizeEdgeInset = InsetsUtil.maxInset(insets);
+
+                  if (areaOrientation == Direction.UP)
+                    insets.bottom = Math.max(minResizeEdgeInset, insets.bottom);
+                  else if (areaOrientation == Direction.DOWN)
+                    insets.top = Math.max(minResizeEdgeInset, insets.top);
+                  else if (areaOrientation == Direction.LEFT)
+                    insets.right = Math.max(minResizeEdgeInset, insets.right);
+                  else
+                    insets.left = Math.max(minResizeEdgeInset, insets.left);
+
+                  return insets;
+                }
+
+                public boolean isBorderOpaque() {
+                  return false;
+                }
+              }).setInsets(new Insets(0, 0, 0, 0)).setBackgroundColor(null);
+        }
+
+        {
+          // Window area
+          final Color top = tpTheme.getBorderColor(Direction.UP);
+          final Color left = tpTheme.getBorderColor(Direction.LEFT);
+          final Color bottom = tpTheme.getBorderColor(Direction.DOWN);
+          final Color right = tpTheme.getBorderColor(Direction.RIGHT);
+
+          final Insets borderInsets = new Insets(top == null ? 0 : 1,
+                                                 left == null ? 0 : 1,
+                                                 bottom == null ? 0 : 1,
+                                                 right == null ? 0 : 1);
+          final boolean borderOpaque =
+              (top != null && top.getAlpha() == 255) || (left != null && left.getAlpha() == 255)
+              || (bottom != null && bottom.getAlpha() == 255) || (right != null && right.getAlpha() == 255);
+
+          final Border windowAreaBorder = new Border() {
+            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+              if (top != null) {
+                g.setColor(top);
+                g.drawLine(x, y, x + width - (right == null ? 1 : 2), y);
+              }
+
+              if (right != null) {
+                g.setColor(right);
+                g.drawLine(x + width - 1, y, x + width - 1, y + height - (bottom == null ? 1 : 2));
+              }
+
+              if (bottom != null) {
+                g.setColor(bottom);
+                g.drawLine(left == null ? x : x + 1, y + height - 1, x + width - 1, y + height - 1);
+              }
+
+              if (left != null) {
+                g.setColor(left);
+                g.drawLine(x, top == null ? y : y + 1, x, y + height - 1);
+              }
+            }
+
+            public Insets getBorderInsets(Component c) {
+              return borderInsets;
+            }
+
+            public boolean isBorderOpaque() {
+              return borderOpaque;
+            }
+          };
+
+          rootProps.getWindowAreaProperties().setInsets(new Insets(2, 2, 2, 2)).setBorder(windowAreaBorder)
+              .setBackgroundColor(null);
+          rootProps.getWindowAreaShapedPanelProperties().setComponentPainter(
+              new SolidColorComponentPainter(BackgroundColorProvider.INSTANCE)).setOpaque(true);
+        }
+
+        {
+          // View title bar
+          ViewTitleBarProperties titleBarProps = rootProps.getViewProperties().getViewTitleBarProperties();
+          titleBarProps.getNormalProperties().getShapedPanelProperties().setDirection(
+              titleBarUI.getRenderingDirection());
+
+          // Normal painter
+          ComponentPainter normalPainter = titleBarUI.getInactiveComponentPainter();
+          if (normalPainter == null)
+            titleBarProps.getNormalProperties().getShapedPanelProperties().getMap().removeValue(
+                ShapedPanelProperties.COMPONENT_PAINTER);
+          else
+            titleBarProps.getNormalProperties().getShapedPanelProperties().setComponentPainter(normalPainter);
+
+          // Focused painter
+          ComponentPainter focusedPainter = titleBarUI.getActiveComponentPainter();
+          if (focusedPainter == null)
+            titleBarProps.getFocusedProperties().getShapedPanelProperties().getMap().removeValue(
+                ShapedPanelProperties.COMPONENT_PAINTER);
+          else
+            titleBarProps.getFocusedProperties().getShapedPanelProperties().setComponentPainter(focusedPainter);
+
+          titleBarProps.setMinimumSizeProvider(titleBarUI.getSizeDimensionProvider()).getNormalProperties()
+              .setTitleVisible(!titleBarUI.isRenderingTitle()).setIconVisible(!titleBarUI.isRenderingIcon());
+
+          titleBarProps.getFocusedProperties().getComponentProperties().setInsets(titleBarUI.getInsets());
+          titleBarProps.getNormalProperties().getComponentProperties().setInsets(titleBarUI.getInsets());
+
+          // Background colors
+          updateBackgroundColor(titleBarProps.getNormalProperties().getComponentProperties(),
+                                titleBarUI.getInactiveBackgroundColor());
+          updateBackgroundColor(titleBarProps.getFocusedProperties().getComponentProperties(),
+                                titleBarUI.getActiveBackgroundColor());
+        }
+
+        {
+          // General
+          rootProps.setDragRectangleBorderWidth(3);
+        }
+      }
+    });
+  }
+
+  private void updateBackgroundColor(ComponentProperties props, Color color) {
+    if (color == null)
+      props.getMap().removeValue(ComponentProperties.BACKGROUND_COLOR);
+    else
+      props.setBackgroundColor(color);
+  }
+}
diff --git a/src/net/infonode/docking/theme/ShapedGradientDockingTheme.java b/src/net/infonode/docking/theme/ShapedGradientDockingTheme.java
new file mode 100644
index 0000000..2e97de1
--- /dev/null
+++ b/src/net/infonode/docking/theme/ShapedGradientDockingTheme.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ShapedGradientDockingTheme.java,v 1.26 2007/01/28 21:25:10 jesper Exp $
+
+package net.infonode.docking.theme;
+
+import net.infonode.docking.properties.RootWindowProperties;
+import net.infonode.gui.colorprovider.ColorBlender;
+import net.infonode.gui.colorprovider.ColorMultiplier;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.UIManagerColorProvider;
+import net.infonode.gui.componentpainter.GradientComponentPainter;
+import net.infonode.gui.componentpainter.SolidColorComponentPainter;
+import net.infonode.gui.shaped.border.RoundedCornerBorder;
+import net.infonode.tabbedpanel.Tab;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.TabbedUtils;
+import net.infonode.tabbedpanel.theme.ShapedGradientTheme;
+import net.infonode.tabbedpanel.titledtab.TitledTabBorderSizePolicy;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+import javax.swing.border.Border;
+import java.awt.*;
+
+/**
+ * A theme with tabs with rounded edges, gradient backgrounds and support for
+ * slopes on left/right side of tab.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.26 $
+ * @since IDW 1.2.0
+ */
+public class ShapedGradientDockingTheme extends DockingWindowsTheme {
+  private RootWindowProperties rootWindowProperties = new RootWindowProperties();
+  private String name;
+
+  /**
+   * Creates a default theme with sloped border on the right side of the tab
+   * (excluding tabs on window bars) and with colors based on the active look
+   * and feel
+   */
+  public ShapedGradientDockingTheme() {
+    this(0f, 0.5f);
+  }
+
+  /**
+   * Creates a theme with the given slopes on the left and right side of the tab
+   * (excluding tabs on window bars) and with colors based on the active look and feel
+   *
+   * @param leftSlope  leaning of left slope defined as left slope width divided by left slope height
+   * @param rightSlope leaning of right slope defined as right slope width divided by right slope height
+   */
+  public ShapedGradientDockingTheme(float leftSlope, float rightSlope) {
+    this(leftSlope,
+         rightSlope,
+         UIManagerColorProvider.TABBED_PANE_DARK_SHADOW,
+         UIManagerColorProvider.TABBED_PANE_HIGHLIGHT,
+         true);
+  }
+
+  /**
+   * Creates a theme with the given slopes on the left and right side of the tab
+   * (excluding tabs on window bars) and with the given colors
+   *
+   * @param leftSlope               leaning of left slope defined as left slope width divided
+   *                                by left slope height
+   * @param rightSlope              leaning of right slope defined as right slope width divided
+   *                                by right slope height
+   * @param lineColor               color provider for the lines
+   * @param highlightColor          color provider for the highlighting, null for no highlighting
+   * @param focusHighlighterEnabled if true the currently focused tab is highlighted
+   */
+  public ShapedGradientDockingTheme(float leftSlope, float rightSlope, ColorProvider lineColor,
+                                    ColorProvider highlightColor, boolean focusHighlighterEnabled) {
+    this(leftSlope, rightSlope, 25, lineColor, highlightColor, focusHighlighterEnabled);
+  }
+
+  /**
+   * Creates a theme with the given slopes on the left and right side of the tab
+   * (excluding tabs on window bars) and with the given colors
+   *
+   * @param leftSlope               leaning of left slope defined as left slope width divided
+   *                                by left slope height
+   * @param rightSlope              leaning of right slope defined as right slope width divided
+   *                                by right slope height
+   * @param slopeHeight             slope height in pixels, used when estimating slope width
+   * @param lineColor               color provider for the lines
+   * @param highlightColor          color provider for the highlighting, null for no highlighting
+   * @param focusHighlighterEnabled if true the currently focused tab is highlighted
+   */
+  public ShapedGradientDockingTheme(float leftSlope, float rightSlope, int slopeHeight, ColorProvider lineColor,
+                                    ColorProvider highlightColor, boolean focusHighlighterEnabled) {
+    final ShapedGradientTheme theme = new ShapedGradientTheme(leftSlope,
+                                                              rightSlope,
+                                                              slopeHeight,
+                                                              lineColor,
+                                                              highlightColor);
+    name = theme.getName();
+
+    int cornerType = 3;
+
+    TabbedPanelProperties tabbedPanelProperties = theme.getTabbedPanelProperties();
+    TitledTabProperties titledTabProperties = theme.getTitledTabProperties();
+
+    // Tab window
+    rootWindowProperties.getTabWindowProperties().getTabbedPanelProperties().addSuperObject(tabbedPanelProperties);
+    rootWindowProperties.getTabWindowProperties().getTabProperties().getTitledTabProperties().addSuperObject(
+        titledTabProperties);
+
+    if (focusHighlighterEnabled) {
+      rootWindowProperties.getTabWindowProperties().getTabProperties().getFocusedProperties().
+          getShapedPanelProperties().setComponentPainter(rootWindowProperties.getTabWindowProperties().
+          getTabProperties().getTitledTabProperties()
+          .getHighlightedProperties()
+          .getShapedPanelProperties()
+          .getComponentPainter());
+      ColorProvider topColor = new ColorMultiplier(theme.getControlColor(), 0.85f);
+      rootWindowProperties.getTabWindowProperties().getTabProperties().getTitledTabProperties().
+          getHighlightedProperties().getShapedPanelProperties().setComponentPainter(new GradientComponentPainter(
+          topColor,
+          theme.getControlColor(),
+          theme.getControlColor(),
+          theme.getControlColor()));
+      rootWindowProperties.getTabWindowProperties().getTabbedPanelProperties().getTabAreaComponentsProperties().
+          getShapedPanelProperties().setComponentPainter(new GradientComponentPainter(
+          new ColorMultiplier(theme.getControlColor(), 1.1f),
+          theme.getControlColor(),
+          theme.getControlColor(),
+          topColor));
+    }
+
+    Border highlightBorder = theme.createTabBorder(theme.getLineColor(),
+                                                   theme.getHighlightColor(),
+                                                   0f,
+                                                   0f,
+                                                   true,
+                                                   true,
+                                                   true,
+                                                   true,
+                                                   false,
+                                                   true,
+                                                   0);
+
+    final int inset = 4;
+    final int extraRaised = 1;
+
+    Border normalBorder = new RoundedCornerBorder(theme.getLineColor(),
+                                                  null,
+                                                  cornerType,
+                                                  cornerType,
+                                                  cornerType,
+                                                  cornerType,
+                                                  true,
+                                                  true,
+                                                  true,
+                                                  true) {
+      private Border calculatedInsetsBorder = theme.createTabBorder(theme.getLineColor(),
+                                                                    null,
+                                                                    0f,
+                                                                    0f,
+                                                                    false,
+                                                                    true,
+                                                                    true,
+                                                                    false,
+                                                                    true,
+                                                                    true,
+                                                                    0);
+
+      protected Polygon createPolygon(Component c, int width, int height) {
+        Polygon p = super.createPolygon(c, width, height);
+        for (int i = 0; i < p.npoints; i++)
+          if (p.xpoints[i] < width / 2)
+            p.xpoints[i] = p.xpoints[i] + (isFirst(c) ? 0 : inset) + extraRaised;
+          else
+            p.xpoints[i] = p.xpoints[i] - inset - extraRaised;
+
+        return p;
+      }
+
+      public Insets getBorderInsets(Component c) {
+        return calculatedInsetsBorder.getBorderInsets(c);
+      }
+
+      private boolean isFirst(Component c) {
+        Tab tab = TabbedUtils.getParentTab(c);
+        if (tab != null && tab.getTabbedPanel() != null)
+          return tab.getTabbedPanel().getTabAt(0) == tab;
+
+        return false;
+      }
+    };
+
+    //Insets insets = new Insets(1, 2 + extraRaised, 1, 2 + extraRaised);
+    TitledTabProperties tabProperties = rootWindowProperties.getWindowBarProperties().getTabWindowProperties()
+        .getTabProperties()
+        .getTitledTabProperties();
+    tabProperties.getNormalProperties().getComponentProperties().setBorder(normalBorder).setInsets(
+        new Insets(1, 0, 1, 2 + extraRaised));
+    tabProperties.getHighlightedProperties().getComponentProperties().setBorder(highlightBorder);
+    tabProperties.setHighlightedRaised(0).setBorderSizePolicy(TitledTabBorderSizePolicy.EQUAL_SIZE);
+
+    rootWindowProperties.getWindowBarProperties().getTabWindowProperties().getTabbedPanelProperties().setTabSpacing(
+        -inset * 2 + 2 - 2 * extraRaised)
+        .getTabAreaComponentsProperties().getComponentProperties()
+        .setBorder(new RoundedCornerBorder(theme.getLineColor(),
+                                           theme.getHighlightColor(),
+                                           cornerType,
+                                           cornerType,
+                                           cornerType,
+                                           cornerType,
+                                           true,
+                                           true,
+                                           true,
+                                           true))
+        .setInsets(new Insets(0, 3, 0, 3));
+
+    rootWindowProperties
+        .setDragRectangleBorderWidth(3)
+
+        .getWindowBarProperties().getComponentProperties()
+        .setInsets(new Insets(2, 0, 2, 0));
+
+    rootWindowProperties.getWindowAreaProperties()
+        .setBorder(null)
+        .setInsets(new Insets(2, 2, 2, 2));
+
+    rootWindowProperties.getComponentProperties().setBackgroundColor(null);
+    rootWindowProperties.getShapedPanelProperties().setComponentPainter(new SolidColorComponentPainter(new ColorBlender(
+        UIManagerColorProvider.TABBED_PANE_BACKGROUND,
+        UIManagerColorProvider.CONTROL_COLOR,
+        0.5f)));
+
+    rootWindowProperties.getWindowAreaShapedPanelProperties().setComponentPainter(
+        new SolidColorComponentPainter(UIManagerColorProvider.CONTROL_COLOR));
+
+    Insets insets = rootWindowProperties.getTabWindowProperties().getTabbedPanelProperties().getContentPanelProperties()
+        .getComponentProperties()
+        .getInsets();
+    if (highlightColor == null)
+      rootWindowProperties.getTabWindowProperties().getTabbedPanelProperties().getContentPanelProperties()
+          .getComponentProperties()
+          .setInsets(new Insets(insets.top, insets.top, insets.top, insets.top));
+  }
+
+  /**
+   * Gets the theme name
+   *
+   * @return name
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Gets the theme RootWindowProperties
+   *
+   * @return the RootWindowProperties
+   */
+  public RootWindowProperties getRootWindowProperties() {
+    return rootWindowProperties;
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/docking/theme/SlimFlatDockingTheme.java b/src/net/infonode/docking/theme/SlimFlatDockingTheme.java
new file mode 100644
index 0000000..1dbe313
--- /dev/null
+++ b/src/net/infonode/docking/theme/SlimFlatDockingTheme.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: SlimFlatDockingTheme.java,v 1.20 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.docking.theme;
+
+import net.infonode.docking.properties.RootWindowProperties;
+import net.infonode.docking.properties.ViewTitleBarStateProperties;
+import net.infonode.docking.properties.WindowBarProperties;
+import net.infonode.docking.properties.WindowTabProperties;
+import net.infonode.gui.icon.button.*;
+import net.infonode.tabbedpanel.TabLayoutPolicy;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.border.TabAreaLineBorder;
+import net.infonode.tabbedpanel.theme.SmallFlatTheme;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import java.awt.*;
+
+/**
+ * A theme very slim theme that doesn't waste any screen space.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.20 $
+ */
+public final class SlimFlatDockingTheme extends DockingWindowsTheme {
+  private RootWindowProperties rootWindowProperties;
+
+  public SlimFlatDockingTheme() {
+    rootWindowProperties = createRootWindowProperties();
+  }
+
+  public String getName() {
+    return "Slim Flat Theme";
+  }
+
+  public RootWindowProperties getRootWindowProperties() {
+    return rootWindowProperties;
+  }
+
+  /**
+   * Create a root window properties object with the property values for this theme.
+   *
+   * @return the root window properties object
+   */
+  public static final RootWindowProperties createRootWindowProperties() {
+    SmallFlatTheme smallFlatTheme = new SmallFlatTheme();
+
+    RootWindowProperties rootWindowProperties = new RootWindowProperties();
+    rootWindowProperties.getWindowAreaProperties()
+        .setInsets(new Insets(0, 0, 0, 0))
+        .setBorder(null);
+
+    rootWindowProperties.getSplitWindowProperties().setDividerSize(3);
+
+    TabbedPanelProperties tpProperties = rootWindowProperties.getTabWindowProperties().getTabbedPanelProperties();
+    tpProperties.addSuperObject(smallFlatTheme.getTabbedPanelProperties());
+    tpProperties.setShadowEnabled(false).setTabLayoutPolicy(TabLayoutPolicy.COMPRESSION);
+    tpProperties.getTabAreaComponentsProperties().getComponentProperties().setInsets(new Insets(0, 1, 0, 1));
+
+    WindowTabProperties tabProperties = rootWindowProperties.getTabWindowProperties().getTabProperties();
+    tabProperties.getTitledTabProperties().addSuperObject(smallFlatTheme.getTitledTabProperties());
+
+    Font font = tabProperties.getTitledTabProperties().getHighlightedProperties().getComponentProperties().getFont();
+    if (font != null)
+      font = font.deriveFont(
+          tabProperties.getTitledTabProperties().getNormalProperties().getComponentProperties().getFont().getSize2D());
+    tabProperties.getTitledTabProperties().getHighlightedProperties().getComponentProperties().setFont(font);
+
+    Icon closeIcon = new CloseIcon(8);
+    Icon restoreIcon = new RestoreIcon(8);
+    Icon minimizeIcon = new MinimizeIcon(8);
+    Icon maximizeIcon = new MaximizeIcon(8);
+    Icon dockIcon = new DockIcon(8);
+    Icon undockIcon = new UndockIcon(8);
+
+    tabProperties.getNormalButtonProperties().getCloseButtonProperties().setIcon(closeIcon);
+    tabProperties.getNormalButtonProperties().getRestoreButtonProperties().setIcon(restoreIcon);
+    tabProperties.getNormalButtonProperties().getMinimizeButtonProperties().setIcon(minimizeIcon);
+    tabProperties.getNormalButtonProperties().getDockButtonProperties().setIcon(dockIcon);
+    tabProperties.getNormalButtonProperties().getUndockButtonProperties().setIcon(undockIcon);
+
+    rootWindowProperties.getTabWindowProperties().getCloseButtonProperties().setIcon(closeIcon);
+    rootWindowProperties.getTabWindowProperties().getRestoreButtonProperties().setIcon(restoreIcon);
+    rootWindowProperties.getTabWindowProperties().getMinimizeButtonProperties().setIcon(minimizeIcon);
+    rootWindowProperties.getTabWindowProperties().getMaximizeButtonProperties().setIcon(maximizeIcon);
+    rootWindowProperties.getTabWindowProperties().getDockButtonProperties().setIcon(dockIcon);
+    rootWindowProperties.getTabWindowProperties().getUndockButtonProperties().setIcon(undockIcon);
+
+    ViewTitleBarStateProperties stateProps = rootWindowProperties.getViewProperties().getViewTitleBarProperties()
+        .getNormalProperties();
+    stateProps.getCloseButtonProperties().setIcon(closeIcon);
+    stateProps.getRestoreButtonProperties().setIcon(restoreIcon);
+    stateProps.getMaximizeButtonProperties().setIcon(maximizeIcon);
+    stateProps.getMinimizeButtonProperties().setIcon(minimizeIcon);
+    stateProps.getDockButtonProperties().setIcon(dockIcon);
+    stateProps.getUndockButtonProperties().setIcon(undockIcon);
+
+    rootWindowProperties.getViewProperties().getViewTitleBarProperties().getNormalProperties().getComponentProperties()
+        .setFont(tabProperties.getTitledTabProperties().getNormalProperties().getComponentProperties().getFont());
+
+    setWindowBarProperties(rootWindowProperties.getWindowBarProperties());
+
+    return rootWindowProperties;
+  }
+
+  private static void setWindowBarProperties(WindowBarProperties windowBarProperties) {
+    windowBarProperties.setMinimumWidth(3);
+
+    Border border = new TabAreaLineBorder(false, true, true, false);
+
+    windowBarProperties.getTabWindowProperties().getTabProperties().getTitledTabProperties().getNormalProperties()
+        .getComponentProperties().setInsets(new Insets(0, 4, 0, 4))
+        .setBorder(border);
+
+    windowBarProperties.getTabWindowProperties().getTabProperties().getTitledTabProperties().getHighlightedProperties()
+        .getComponentProperties().setBorder(border);
+  }
+
+  /**
+   * Create a window bar properties object with the property values for this theme.
+   *
+   * @return the root window properties object
+   * @deprecated the window bar properties are now included in the root window properties
+   */
+  public static final WindowBarProperties createWindowBarProperties() {
+    return new WindowBarProperties();
+  }
+}
diff --git a/src/net/infonode/docking/theme/SoftBlueIceDockingTheme.java b/src/net/infonode/docking/theme/SoftBlueIceDockingTheme.java
new file mode 100644
index 0000000..7312d9f
--- /dev/null
+++ b/src/net/infonode/docking/theme/SoftBlueIceDockingTheme.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SoftBlueIceDockingTheme.java,v 1.18 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.theme;
+
+import net.infonode.docking.properties.RootWindowProperties;
+import net.infonode.gui.colorprovider.ColorBlender;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.componentpainter.SolidColorComponentPainter;
+import net.infonode.gui.shaped.border.FixedInsetsShapedBorder;
+import net.infonode.gui.shaped.border.ShapedBorder;
+import net.infonode.tabbedpanel.TabAreaProperties;
+import net.infonode.tabbedpanel.theme.SoftBlueIceTheme;
+
+import java.awt.*;
+
+/**
+ * A light blue theme with gradients and rounded corners.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.18 $
+ */
+public class SoftBlueIceDockingTheme extends DockingWindowsTheme {
+  private RootWindowProperties rootWindowProperties = new RootWindowProperties();
+  private boolean slim;
+
+  /**
+   * Create a theme with default settings.
+   */
+  public SoftBlueIceDockingTheme() {
+    this(false);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param slim if true there is less spacing in the tab area
+   */
+  public SoftBlueIceDockingTheme(boolean slim) {
+    this(SoftBlueIceTheme.DEFAULT_DARK_COLOR, SoftBlueIceTheme.DEFAULT_LIGHT_COLOR, 4, slim);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param darkColor  the dark color used in the gradients
+   * @param lightColor the light color used in the gradients
+   * @param cornerType how much rounding to apply to corners
+   * @param slim       if true there is less spacing in the tab area
+   */
+  public SoftBlueIceDockingTheme(ColorProvider darkColor, ColorProvider lightColor, int cornerType, boolean slim) {
+    SoftBlueIceTheme theme = new SoftBlueIceTheme(darkColor, lightColor, cornerType);
+    this.slim = slim;
+
+    if (slim) {
+      theme.getTabbedPanelProperties().getTabAreaProperties().getComponentProperties()
+          .setBorder(new FixedInsetsShapedBorder(
+              new Insets(0, 0, 0, 0),
+              (ShapedBorder) theme.getTabbedPanelProperties().getTabAreaProperties().getComponentProperties()
+                  .getBorder()));
+
+      theme.getTabbedPanelProperties().getTabAreaProperties().getComponentProperties().setInsets(
+          new Insets(0, 0, 0, 0));
+      theme.getTabbedPanelProperties().setTabSpacing(-1);
+      theme.getTabbedPanelProperties().getTabAreaComponentsProperties().getComponentProperties().setInsets(
+          new Insets(2, 2, 2, 2));
+    }
+
+    rootWindowProperties.getWindowAreaProperties()
+        .setBorder(null)
+        .setInsets(new Insets(2, 2, 2, 2));
+    rootWindowProperties.getWindowAreaShapedPanelProperties().setComponentPainter(
+        new SolidColorComponentPainter(new ColorBlender(darkColor, lightColor, 0.5f)));
+
+    rootWindowProperties.getTabWindowProperties().getTabbedPanelProperties().addSuperObject(
+        theme.getTabbedPanelProperties());
+    rootWindowProperties.getTabWindowProperties().getTabProperties().getTitledTabProperties().addSuperObject(
+        theme.getTitledTabProperties());
+
+    rootWindowProperties.getShapedPanelProperties().
+        setComponentPainter(theme.getTabbedPanelProperties().getTabAreaProperties().getShapedPanelProperties().
+            getComponentPainter());
+
+    rootWindowProperties.getTabWindowProperties().getTabbedPanelProperties().getContentPanelProperties()
+        .getShapedPanelProperties()
+        .setClipChildren(true);
+    rootWindowProperties.getViewProperties().getViewTitleBarProperties().getNormalProperties().getComponentProperties()
+        .setForegroundColor(Color.BLACK)
+        .setInsets(new Insets(0, 2, 0, 2));
+    rootWindowProperties.getViewProperties().getViewTitleBarProperties().getNormalProperties()
+        .getShapedPanelProperties()
+        .setComponentPainter(null)
+        .setOpaque(false);
+    rootWindowProperties.getViewProperties().getViewTitleBarProperties().getFocusedProperties()
+        .getShapedPanelProperties()
+        .setComponentPainter(null);//.setOpaque(false);
+    rootWindowProperties.getViewProperties().getViewTitleBarProperties().getFocusedProperties().getComponentProperties()
+        .setForegroundColor(Color.BLACK);
+
+    TabAreaProperties p = rootWindowProperties.getWindowBarProperties().getTabWindowProperties().
+        getTabbedPanelProperties().getTabAreaProperties();
+
+    p.getShapedPanelProperties().setComponentPainter(null);
+    p.getComponentProperties().setBorder(null);
+
+    rootWindowProperties.getWindowBarProperties().getTabWindowProperties().
+        getTabbedPanelProperties().getTabAreaComponentsProperties().getComponentProperties().setBorder(null);
+
+    rootWindowProperties.getWindowBarProperties().getTabWindowProperties().
+        getTabbedPanelProperties().getContentPanelProperties().getShapedPanelProperties().setOpaque(false);
+  }
+
+  public String getName() {
+    return "Soft Blue Ice Theme" + (slim ? " - Slim" : "");
+  }
+
+  public RootWindowProperties getRootWindowProperties() {
+    return rootWindowProperties;
+  }
+}
diff --git a/src/net/infonode/docking/theme/internal/laftheme/TitleBarUI.java b/src/net/infonode/docking/theme/internal/laftheme/TitleBarUI.java
new file mode 100644
index 0000000..9e04fff
--- /dev/null
+++ b/src/net/infonode/docking/theme/internal/laftheme/TitleBarUI.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitleBarUI.java,v 1.13 2009/02/05 15:57:55 jesper Exp $
+package net.infonode.docking.theme.internal.laftheme;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.FilteredImageSource;
+import java.awt.image.RGBImageFilter;
+import java.beans.PropertyVetoException;
+
+import javax.swing.*;
+
+import net.infonode.docking.View;
+import net.infonode.docking.internal.ViewTitleBar;
+import net.infonode.gui.DimensionProvider;
+import net.infonode.gui.DynamicUIManager;
+import net.infonode.gui.DynamicUIManagerListener;
+import net.infonode.gui.componentpainter.ComponentPainter;
+import net.infonode.gui.componentpainter.GradientComponentPainter;
+import net.infonode.tabbedpanel.theme.internal.laftheme.SizeIcon;
+import net.infonode.util.ColorUtil;
+import net.infonode.util.Direction;
+
+public class TitleBarUI {
+  private static final int NUM_FADE_COLORS = 25;
+
+  private static final int BUTTON_OFFSET = 2;
+
+  private static final int RIGHT_INSET = 4;
+
+  private final boolean showing = true;
+
+  private boolean enabled;
+
+  private final DynamicUIManagerListener uiListener = new DynamicUIManagerListener() {
+
+    public void lookAndFeelChanged() {
+      doUpdate();
+    }
+
+    public void propertiesChanging() {
+      listener.updating();
+    }
+
+    public void propertiesChanged() {
+      doUpdate();
+    }
+
+    public void lookAndFeelChanging() {
+      listener.updating();
+    }
+
+  };
+
+  private class IFrame extends JInternalFrame {
+    public IFrame() {
+    }
+
+    public void updateUI() {
+      super.updateUI();
+
+      setClosable(false);
+      setIconifiable(false);
+      setMaximizable(false);
+      setFocusable(false);
+    }
+
+    public void setSelectedActivated(boolean selected) {
+      try {
+        setSelected(selected);
+      }
+      catch (PropertyVetoException e) {
+        e.printStackTrace();
+      }
+    }
+
+    public boolean isShowing() {
+      return showing;
+    }
+  }
+
+  private final Color[] fadeSelectedColors = new Color[NUM_FADE_COLORS];
+
+  private final Color[] fadeNormalColors = new Color[NUM_FADE_COLORS];
+
+  private final IFrame iFrame = new IFrame();
+
+  private JFrame frame;
+
+  private Dimension reportedMinimumSize;
+
+  private Dimension minimumSize;
+
+  private Insets iFrameInsets;
+
+  private Color inactiveBackgroundColor;
+
+  private Color activeBackgroundColor;
+
+  private Color foundBackgroundColor;
+
+  private boolean skipIFrame = false;
+
+  private final ComponentPainter activeComponentPainter = new ComponentPainter() {
+    public void paint(Component component, Graphics g, int x, int y, int width, int height) {
+    }
+
+    public void paint(Component component,
+                      Graphics g,
+                      int x,
+                      int y,
+                      int width,
+                      int height,
+                      Direction direction,
+                      boolean horizontalFlip,
+                      boolean verticalFlip) {
+      g.translate(-x, -y);
+      paintTitleBar(component, g, true, width, height, Direction.UP);
+      g.translate(x, y);
+    }
+
+    public boolean isOpaque(Component component) {
+      return false;
+    }
+
+    public Color getColor(Component component) {
+      return getActiveBackgroundColor();
+    }
+  };
+
+  private final ComponentPainter inactiveComponentPainter = new ComponentPainter() {
+    public void paint(Component component, Graphics g, int x, int y, int width, int height) {
+    }
+
+    public void paint(Component component,
+                      Graphics g,
+                      int x,
+                      int y,
+                      int width,
+                      int height,
+                      Direction direction,
+                      boolean horizontalFlip,
+                      boolean verticalFlip) {
+      g.translate(-x, -y);
+      paintTitleBar(component, g, false, width, height, Direction.UP);
+      g.translate(x, y);
+    }
+
+    public boolean isOpaque(Component component) {
+      return false;
+    }
+
+    public Color getColor(Component component) {
+      return getInactiveBackgroundColor();
+    }
+  };
+
+  private final TitleBarUIListener listener;
+
+  public TitleBarUI(TitleBarUIListener listener, boolean enabled) {
+    this.enabled = enabled;
+    this.listener = listener;
+  }
+
+  public void setEnabled(boolean enabled) {
+    this.enabled = enabled;
+  }
+
+  public void dispose() {
+    DynamicUIManager.getInstance().removePrioritizedListener(uiListener);
+    frame.removeAll();
+    frame.dispose();
+  }
+
+  public void init() {
+    DynamicUIManager.getInstance().addPrioritizedListener(uiListener);
+
+    frame = new JFrame();
+
+    frame.getContentPane().setLayout(null);
+    frame.getContentPane().add(iFrame);
+    frame.pack();
+
+    listener.updating();
+    update();
+  }
+
+  private void doUpdate() {
+    setEnabled(false);
+    SwingUtilities.updateComponentTreeUI(frame);
+
+    update();
+  }
+
+  private void update() {
+    SwingUtilities.invokeLater(new Runnable() {
+
+      public void run() {
+        iFrame.setClosable(false);
+        iFrame.setMaximizable(false);
+        iFrame.setIconifiable(false);
+        iFrame.setBounds(0, 0, 50, 50);
+        iFrame.setResizable(false);
+
+        iFrame.setVisible(true);
+        iFrame.setTitle(" ");
+
+        iFrame.setFrameIcon(SizeIcon.EMPTY);
+
+        {
+          // Insets
+          iFrameInsets = (Insets) iFrame.getInsets().clone();
+          if (UIManager.getLookAndFeel().getClass().getName().indexOf(".MotifLookAndFeel") != -1) {
+            iFrameInsets.left += 19;
+          }
+        }
+
+        {
+          // Size
+          reportedMinimumSize = iFrame.getPreferredSize();
+          minimumSize = new Dimension(Math.max(0, reportedMinimumSize.width - iFrameInsets.left - iFrameInsets.right),
+              reportedMinimumSize.height - iFrameInsets.top - iFrameInsets.bottom);
+        }
+
+        String lafName = UIManager.getLookAndFeel().getClass().getName();
+        skipIFrame = lafName.indexOf("GTKLookAndFeel") != -1
+        || (lafName.indexOf(".WindowsLookAndFeel") != -1 || UIManager.getLookAndFeel().getClass().getName()
+            .indexOf(".Office2003LookAndFeel") != -1) && Toolkit.getDefaultToolkit().getDesktopProperty(
+            "win.xpstyle.themeActive") != null;
+
+        estimateBackgroundColors();
+
+        setEnabled(true);
+        listener.updated();
+      }
+
+    });
+  }
+
+  private void estimateBackgroundColors() {
+    activeBackgroundColor = estimateBackgroundColor(true);
+
+    inactiveBackgroundColor = estimateBackgroundColor(false);
+
+    double factor = 255 / fadeSelectedColors.length;
+
+    for (int i = 0; i < fadeSelectedColors.length; i++) {
+      if (activeBackgroundColor != null)
+        fadeSelectedColors[i] = new Color(activeBackgroundColor.getRed(), activeBackgroundColor.getGreen(),
+            activeBackgroundColor.getBlue(), (int) ((i + 1) * factor));
+      if (inactiveBackgroundColor != null)
+        fadeNormalColors[i] =
+          new Color(inactiveBackgroundColor.getRed(), inactiveBackgroundColor.getGreen(), inactiveBackgroundColor
+              .getBlue(), (int) ((i + 1) * factor));
+    }
+  }
+
+  private Color estimateBackgroundColor(boolean selected) {
+    setSize(400);
+
+    iFrame.setSelectedActivated(selected);
+
+    BufferedImage img = new BufferedImage(iFrame.getWidth(), iFrame.getHeight(), BufferedImage.TYPE_INT_ARGB);
+
+    int x = iFrame.getWidth() - iFrameInsets.right - 3;
+    int y = iFrameInsets.top + 3;
+
+    final int px = x;
+    final int py = y;
+
+    RGBImageFilter colorFilter = new RGBImageFilter() {
+      public int filterRGB(int x, int y, int rgb) {
+        if (px == x && py == y) {
+          int r = (rgb >> 16) & 0xff;
+          int g = (rgb >> 8) & 0xff;
+          int b = (rgb) & 0xff;
+          int a = (rgb >> 24) & 0xff;
+
+          foundBackgroundColor = new Color(r, g, b, a);
+        }
+
+        return rgb;
+      }
+    };
+
+    FilteredImageSource source = new FilteredImageSource(img.getSource(), colorFilter);
+    iFrame.paint(img.getGraphics());
+
+    BufferedImage img2 = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
+    img2.getGraphics().drawImage(Toolkit.getDefaultToolkit().createImage(source), 0, 0, null);
+
+    return foundBackgroundColor;
+  }
+
+  public DimensionProvider getSizeDimensionProvider() {
+    return skipIFrame ? null : new DimensionProvider() {
+      public Dimension getDimension(Component c) {
+        return minimumSize;
+      }
+    };
+  }
+
+  public void paintTitleBar(Component c, Graphics g, boolean selected, int width, int height, Direction d) {
+    if (enabled) {
+      View view = findView(c);
+      if (view == null)
+        return;
+
+      setTitleAndIcon(view.getViewProperties().getViewTitleBarProperties().getNormalProperties().getTitle(),
+          view.getViewProperties()
+          .getViewTitleBarProperties().getNormalProperties().getIcon());
+
+      iFrame.setSelectedActivated(selected);
+
+      setSize(width);
+
+      Shape clip = g.getClip();
+
+      g.clipRect(0, 0, width, reportedMinimumSize.height - iFrameInsets.top - iFrameInsets.bottom);
+      g.translate(-iFrameInsets.left, -iFrameInsets.top);
+      iFrame.paint(g);
+      g.translate(iFrameInsets.left, iFrameInsets.top);
+      g.setClip(clip);
+
+      paintSolidButtonBackground(c, g, selected);
+    }
+  }
+
+  private void paintSolidButtonBackground(Component c, Graphics g, boolean selected) {
+    ViewTitleBar bar = (ViewTitleBar) c;
+
+    JComponent[] comps = bar.getRightTitleComponents();
+    if (comps.length > 0) {
+      int width = 0;
+
+      for (int i = 0; i < comps.length; i++) {
+        if (comps[i].isVisible())
+          width += comps[i].getWidth();
+      }
+
+      Color background = selected ? activeBackgroundColor : inactiveBackgroundColor;
+      Color[] fadeColors = selected ? fadeSelectedColors : fadeNormalColors;
+
+      for (int i = 0; i < fadeColors.length; i++) {
+        g.setColor(fadeColors[i]);
+        int xPos = c.getWidth() - width - (fadeColors.length - i) - RIGHT_INSET;
+        g.drawLine(xPos, BUTTON_OFFSET, xPos, c.getHeight() - 2 * BUTTON_OFFSET);
+      }
+
+      g.setColor(background);
+      g.fillRect(c.getWidth() - width - RIGHT_INSET,
+          BUTTON_OFFSET,
+          width + RIGHT_INSET,
+          c.getHeight() - 2 * BUTTON_OFFSET);
+    }
+  }
+
+  private void setTitleAndIcon(String title, Icon icon) {
+    iFrame.setTitle(title);
+    iFrame.setFrameIcon(icon == null ? SizeIcon.EMPTY : icon);
+  }
+
+  private View findView(Component c) {
+    if (c == null || c instanceof View)
+      return (View) c;
+
+    return findView(c.getParent());
+  }
+
+  private void setSize(int width) {
+    iFrame.setSize(width + iFrameInsets.left + iFrameInsets.right, reportedMinimumSize.height);
+    iFrame.invalidate();
+    iFrame.validate();
+  }
+
+  public boolean isRenderingIcon() {
+    return !skipIFrame;
+  }
+
+  public boolean isRenderingTitle() {
+    return !skipIFrame;
+  }
+
+  public Direction getRenderingDirection() {
+    return Direction.RIGHT;
+  }
+
+  public ComponentPainter getInactiveComponentPainter() {
+    if (!skipIFrame)
+      return inactiveComponentPainter;
+
+    Color bkg = UIManager.getColor("InternalFrame.inactiveTitleBackground");
+    if (bkg == null)
+      bkg = inactiveBackgroundColor;
+
+    return createComponentPainter(bkg, UIManager.getColor("InternalFrame.inactiveTitleGradient"));
+  }
+
+  public ComponentPainter getActiveComponentPainter() {
+    if (!skipIFrame)
+      return activeComponentPainter;
+
+    Color bkg = UIManager.getColor("InternalFrame.activeTitleBackground");
+    if (bkg == null)
+      bkg = activeBackgroundColor;
+
+    return createComponentPainter(bkg, UIManager.getColor("InternalFrame.activeTitleGradient"));
+  }
+
+  public Insets getInsets() {
+    return skipIFrame ? new Insets(2, 2, 2, 2) : new Insets(0, 0, 0, RIGHT_INSET);
+  }
+
+  public Color getInactiveBackgroundColor() {
+    return inactiveBackgroundColor;
+  }
+
+  public Color getActiveBackgroundColor() {
+    return activeBackgroundColor;
+  }
+
+  private ComponentPainter createComponentPainter(final Color background, final Color gradient) {
+    final Color avgColor = ColorUtil.blend(background, gradient, 0.5);
+    final ComponentPainter painter = createGradientSegmentPainter(background, gradient, true);
+
+    return new ComponentPainter() {
+      public void paint(Component component, Graphics g, int x, int y, int width, int height) {
+      }
+
+      public void paint(Component component,
+                        Graphics g,
+                        int x,
+                        int y,
+                        int width,
+                        int height,
+                        Direction direction,
+                        boolean horizontalFlip,
+                        boolean verticalFlip) {
+        g.setColor(gradient);
+        g.drawLine(x, y, x + width - 1, y);
+        g.drawLine(x, y, x, y + height - 1);
+
+        g.setColor(avgColor);
+        g.drawRect(x + 1, y + 1, width - 3, height - 3);
+
+        painter.paint(component, g, x + 2, y + 2, width - 4, height - 4, direction, horizontalFlip, verticalFlip);
+
+        g.setColor(background);
+        g.drawLine(x + 1, height - 1 + y, x + width - 1, height - 1 + y);
+        g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
+      }
+
+      public boolean isOpaque(Component component) {
+        return painter.isOpaque(component);
+      }
+
+      public Color getColor(Component component) {
+        return painter.getColor(component);
+      }
+    };
+  }
+
+  private ComponentPainter createGradientSegmentPainter(Color background, Color gradient, boolean flip) {
+    if (background != null) {
+      gradient = gradient == null ? background : gradient;
+      background = ColorUtil.mult(background, flip ? 1.05 : 0.90);
+      gradient = ColorUtil.mult(gradient, flip ? 0.90 : 1.05);
+
+      return new GradientComponentPainter(background, background, gradient, gradient);
+    }
+
+    return null;
+  }
+}
diff --git a/src/net/infonode/docking/theme/internal/laftheme/TitleBarUIListener.java b/src/net/infonode/docking/theme/internal/laftheme/TitleBarUIListener.java
new file mode 100644
index 0000000..180d72e
--- /dev/null
+++ b/src/net/infonode/docking/theme/internal/laftheme/TitleBarUIListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitleBarUIListener.java,v 1.2 2005/12/04 13:35:12 jesper Exp $
+package net.infonode.docking.theme.internal.laftheme;
+
+public interface TitleBarUIListener {
+  void updating();
+
+  void updated();
+}
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_close_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_close_hovered.png
new file mode 100644
index 0000000..a687ad1
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_close_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_close_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_close_normal.png
new file mode 100644
index 0000000..884cbd2
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_close_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_close_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_close_pressed.png
new file mode 100644
index 0000000..98c6562
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_close_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_dock_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_dock_hovered.png
new file mode 100644
index 0000000..e81192e
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_dock_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_dock_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_dock_normal.png
new file mode 100644
index 0000000..644b75b
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_dock_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_dock_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_dock_pressed.png
new file mode 100644
index 0000000..ee1d2f4
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_dock_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_maximize_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_maximize_hovered.png
new file mode 100644
index 0000000..ac1f1d4
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_maximize_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_maximize_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_maximize_normal.png
new file mode 100644
index 0000000..5a3721d
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_maximize_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_maximize_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_maximize_pressed.png
new file mode 100644
index 0000000..0650c4c
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_maximize_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_minimize_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_minimize_hovered.png
new file mode 100644
index 0000000..9782db3
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_minimize_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_minimize_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_minimize_normal.png
new file mode 100644
index 0000000..3024c46
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_minimize_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_minimize_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_minimize_pressed.png
new file mode 100644
index 0000000..8da0357
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_minimize_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_restore_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_restore_hovered.png
new file mode 100644
index 0000000..bbb67e2
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_restore_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_restore_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_restore_normal.png
new file mode 100644
index 0000000..e0e72a4
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_restore_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_restore_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_restore_pressed.png
new file mode 100644
index 0000000..9523588
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_restore_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_undock_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_undock_hovered.png
new file mode 100644
index 0000000..9fbc89e
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_undock_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_undock_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_undock_normal.png
new file mode 100644
index 0000000..0de5003
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_undock_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bar_button_undock_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_undock_pressed.png
new file mode 100644
index 0000000..6af1216
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bar_button_undock_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_down.png b/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_down.png
new file mode 100644
index 0000000..85c2296
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_down.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_left.png b/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_left.png
new file mode 100644
index 0000000..bd8540a
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_left.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_right.png b/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_right.png
new file mode 100644
index 0000000..904a638
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_right.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_up.png b/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_up.png
new file mode 100644
index 0000000..6b7c6fb
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bartab_hovered_up.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_down.png b/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_down.png
new file mode 100644
index 0000000..68e03a0
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_down.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_left.png b/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_left.png
new file mode 100644
index 0000000..a8ea74d
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_left.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_right.png b/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_right.png
new file mode 100644
index 0000000..f48e245
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_right.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_up.png b/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_up.png
new file mode 100644
index 0000000..5d46471
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/bartab_normal_up.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_close_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/button_close_hovered.png
new file mode 100644
index 0000000..45038a1
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_close_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_close_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/button_close_normal.png
new file mode 100644
index 0000000..c696cfb
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_close_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_close_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/button_close_pressed.png
new file mode 100644
index 0000000..a1072df
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_close_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_dock_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/button_dock_hovered.png
new file mode 100644
index 0000000..645ebf1
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_dock_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_dock_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/button_dock_normal.png
new file mode 100644
index 0000000..8c4fbb7
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_dock_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_dock_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/button_dock_pressed.png
new file mode 100644
index 0000000..8d8a66c
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_dock_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_maximize_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/button_maximize_hovered.png
new file mode 100644
index 0000000..328ed14
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_maximize_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_maximize_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/button_maximize_normal.png
new file mode 100644
index 0000000..19ea7c5
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_maximize_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_maximize_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/button_maximize_pressed.png
new file mode 100644
index 0000000..72388cc
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_maximize_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_minimize_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/button_minimize_hovered.png
new file mode 100644
index 0000000..69c9a61
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_minimize_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_minimize_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/button_minimize_normal.png
new file mode 100644
index 0000000..a151397
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_minimize_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_minimize_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/button_minimize_pressed.png
new file mode 100644
index 0000000..fea0df9
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_minimize_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_restore_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/button_restore_hovered.png
new file mode 100644
index 0000000..e7d091a
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_restore_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_restore_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/button_restore_normal.png
new file mode 100644
index 0000000..ce43614
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_restore_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_restore_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/button_restore_pressed.png
new file mode 100644
index 0000000..a2fbd55
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_restore_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_tab_close.png b/src/net/infonode/docking/theme/internal/resource/xp/button_tab_close.png
new file mode 100644
index 0000000..104fe8e
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_tab_close.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_tab_dock.png b/src/net/infonode/docking/theme/internal/resource/xp/button_tab_dock.png
new file mode 100644
index 0000000..c35c59a
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_tab_dock.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_tab_minimize.png b/src/net/infonode/docking/theme/internal/resource/xp/button_tab_minimize.png
new file mode 100644
index 0000000..bc331bb
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_tab_minimize.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_tab_restore.png b/src/net/infonode/docking/theme/internal/resource/xp/button_tab_restore.png
new file mode 100644
index 0000000..4422fb7
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_tab_restore.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_tab_undock.png b/src/net/infonode/docking/theme/internal/resource/xp/button_tab_undock.png
new file mode 100644
index 0000000..244a0cf
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_tab_undock.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_undock_hovered.png b/src/net/infonode/docking/theme/internal/resource/xp/button_undock_hovered.png
new file mode 100644
index 0000000..99751c9
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_undock_hovered.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_undock_normal.png b/src/net/infonode/docking/theme/internal/resource/xp/button_undock_normal.png
new file mode 100644
index 0000000..88a9adc
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_undock_normal.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/button_undock_pressed.png b/src/net/infonode/docking/theme/internal/resource/xp/button_undock_pressed.png
new file mode 100644
index 0000000..13cc33d
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/button_undock_pressed.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_down.png b/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_down.png
new file mode 100644
index 0000000..4059702
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_down.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_left.png b/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_left.png
new file mode 100644
index 0000000..85a497d
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_left.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_right.png b/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_right.png
new file mode 100644
index 0000000..275952c
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_right.png differ
diff --git a/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_up.png b/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_up.png
new file mode 100644
index 0000000..8386ac8
Binary files /dev/null and b/src/net/infonode/docking/theme/internal/resource/xp/tab_selected_nofocus_up.png differ
diff --git a/src/net/infonode/docking/theme/package.html b/src/net/infonode/docking/theme/package.html
new file mode 100644
index 0000000..a04f906
--- /dev/null
+++ b/src/net/infonode/docking/theme/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Contains theme classes for docking windows.
+</body>
+</html>
diff --git a/src/net/infonode/docking/title/DockingWindowTitleProvider.java b/src/net/infonode/docking/title/DockingWindowTitleProvider.java
new file mode 100644
index 0000000..c6c62da
--- /dev/null
+++ b/src/net/infonode/docking/title/DockingWindowTitleProvider.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowTitleProvider.java,v 1.4 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.title;
+
+import net.infonode.docking.DockingWindow;
+
+/**
+ * Returns a text title for a {@link DockingWindow}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.3.0
+ */
+public interface DockingWindowTitleProvider {
+  /**
+   * Returns a text title for a {@link DockingWindow}.
+   *
+   * @param window the docking window
+   * @return the text title
+   */
+  String getTitle(DockingWindow window);
+}
diff --git a/src/net/infonode/docking/title/DockingWindowTitleProviderProperty.java b/src/net/infonode/docking/title/DockingWindowTitleProviderProperty.java
new file mode 100644
index 0000000..2a49f7f
--- /dev/null
+++ b/src/net/infonode/docking/title/DockingWindowTitleProviderProperty.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingWindowTitleProviderProperty.java,v 1.4 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.title;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A property that has a {@link DockingWindowTitleProvider} object as value.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.3.0
+ */
+public class DockingWindowTitleProviderProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public DockingWindowTitleProviderProperty(PropertyGroup group,
+                                            String name,
+                                            String description,
+                                            PropertyValueHandler valueHandler) {
+    super(group, name, DockingWindowTitleProvider.class, description, valueHandler);
+  }
+
+  /**
+   * Gets the {@link DockingWindowTitleProvider} value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the {@link DockingWindowTitleProvider} value of this property in a value container
+   */
+  public DockingWindowTitleProvider get(Object valueContainer) {
+    return (DockingWindowTitleProvider) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the {@link DockingWindowTitleProvider} value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param provider       the value
+   */
+  public void set(Object valueContainer, DockingWindowTitleProvider provider) {
+    setValue(valueContainer, provider);
+  }
+}
diff --git a/src/net/infonode/docking/title/LengthLimitedDockingWindowTitleProvider.java b/src/net/infonode/docking/title/LengthLimitedDockingWindowTitleProvider.java
new file mode 100644
index 0000000..c61f933
--- /dev/null
+++ b/src/net/infonode/docking/title/LengthLimitedDockingWindowTitleProvider.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: LengthLimitedDockingWindowTitleProvider.java,v 1.6 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.title;
+
+import net.infonode.docking.AbstractTabWindow;
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.View;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+
+/**
+ * A docking window title provider that constructs a window title from the views inside the window. It adds view
+ * titles until the window title reaches a specified length. If not all view titles fit into the window title,
+ * primarily titles from view inside selected tabs are used.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ * @since IDW 1.3.0
+ */
+public class LengthLimitedDockingWindowTitleProvider implements DockingWindowTitleProvider, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private int maxLength;
+
+  /**
+   * Constructor.
+   *
+   * @param maxLength if the title exceeds this length no more view titles are added to it
+   */
+  public LengthLimitedDockingWindowTitleProvider(int maxLength) {
+    this.maxLength = maxLength;
+  }
+
+  public String getTitle(DockingWindow window) {
+    ArrayList viewTitles = new ArrayList();
+    ArrayList viewPrimary = new ArrayList();
+    getViews(window, viewTitles, viewPrimary, true);
+
+    int length = 0;
+
+    for (int i = 0; i < viewTitles.size(); i++) {
+      if (((Boolean) viewPrimary.get(i)).booleanValue()) {
+        length += ((String) viewTitles.get(i)).length();
+      }
+    }
+
+    StringBuffer title = new StringBuffer(40);
+    int count = 0;
+
+    for (int i = 0; i < viewTitles.size() && title.length() < maxLength; i++) {
+      boolean primary = ((Boolean) viewPrimary.get(i)).booleanValue();
+
+      if (primary || length < maxLength) {
+        if (title.length() > 0)
+          title.append(", ");
+
+        title.append((String) viewTitles.get(i));
+        count++;
+
+        if (!primary)
+          length += ((String) viewTitles.get(i)).length();
+      }
+    }
+
+    if (count < viewTitles.size())
+      title.append(", ...");
+
+    return title.toString();
+  }
+
+  private void getViews(DockingWindow window, ArrayList viewTitles, ArrayList viewPrimary, boolean primary) {
+    if (window == null)
+      return;
+    else if (window instanceof View) {
+      viewTitles.add(((View) window).getViewProperties().getTitle());
+      viewPrimary.add(Boolean.valueOf(primary));
+    }
+    else if (window instanceof AbstractTabWindow) {
+      DockingWindow selected = ((AbstractTabWindow) window).getSelectedWindow();
+
+      for (int i = 0; i < window.getChildWindowCount(); i++) {
+        getViews(window.getChildWindow(i), viewTitles, viewPrimary, selected == window.getChildWindow(i) && primary);
+      }
+    }
+    else {
+      for (int i = 0; i < window.getChildWindowCount(); i++) {
+        getViews(window.getChildWindow(i), viewTitles, viewPrimary, primary);
+      }
+    }
+  }
+
+}
diff --git a/src/net/infonode/docking/title/SimpleDockingWindowTitleProvider.java b/src/net/infonode/docking/title/SimpleDockingWindowTitleProvider.java
new file mode 100644
index 0000000..581bba2
--- /dev/null
+++ b/src/net/infonode/docking/title/SimpleDockingWindowTitleProvider.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SimpleDockingWindowTitleProvider.java,v 1.5 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.title;
+
+import net.infonode.docking.DockingWindow;
+import net.infonode.docking.View;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ * A docking window title provider that concatenates all the titles of all the views contained in a window.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ * @since IDW 1.3.0
+ */
+public class SimpleDockingWindowTitleProvider implements DockingWindowTitleProvider, Serializable {
+  private static final long serialVersionUID = 1;
+
+  public static final SimpleDockingWindowTitleProvider INSTANCE = new SimpleDockingWindowTitleProvider();
+
+  /**
+   * Constructor.
+   */
+  private SimpleDockingWindowTitleProvider() {
+  }
+
+  public String getTitle(DockingWindow window) {
+    StringBuffer title = new StringBuffer(40);
+    getTitle(window, title);
+    return title.toString();
+  }
+
+  private void getTitle(DockingWindow window, StringBuffer title) {
+    if (window == null)
+      return;
+    else if (window instanceof View) {
+      if (title.length() > 0)
+        title.append(", ");
+
+      title.append(((View) window).getViewProperties().getTitle());
+    }
+    else {
+      for (int i = 0; i < window.getChildWindowCount(); i++) {
+        getTitle(window.getChildWindow(i), title);
+      }
+    }
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/docking/title/package.html b/src/net/infonode/docking/title/package.html
new file mode 100644
index 0000000..118eac2
--- /dev/null
+++ b/src/net/infonode/docking/title/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Docking window title classes.
+</body>
+</html>
diff --git a/src/net/infonode/docking/util/AbstractViewMap.java b/src/net/infonode/docking/util/AbstractViewMap.java
new file mode 100644
index 0000000..9e02c9c
--- /dev/null
+++ b/src/net/infonode/docking/util/AbstractViewMap.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractViewMap.java,v 1.11 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.util;
+
+import net.infonode.docking.View;
+import net.infonode.docking.ViewSerializer;
+
+import javax.swing.*;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Base class for view maps.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.11 $
+ * @since IDW 1.1.0
+ */
+abstract public class AbstractViewMap implements ViewFactoryManager, ViewSerializer {
+  private HashMap viewMap = new HashMap();
+  private ArrayList views = new ArrayList(20);
+
+  abstract protected void writeViewId(Object id, ObjectOutputStream out) throws IOException;
+
+  abstract protected Object readViewId(ObjectInputStream in) throws IOException;
+
+  /**
+   * Returns the number of views in this map.
+   *
+   * @return the number of views in this map
+   */
+  public int getViewCount() {
+    return viewMap.size();
+  }
+
+  /**
+   * Returns the view at a specific index.
+   * The view index is the same as the number of views in the map when the view was added to the map.
+   *
+   * @param index the view index
+   * @return the view at the index
+   */
+  public View getViewAtIndex(int index) {
+    return (View) views.get(index);
+  }
+
+  public ViewFactory[] getViewFactories() {
+    ArrayList f = new ArrayList();
+
+    for (int i = 0; i < views.size(); i++) {
+      final View view = (View) views.get(i);
+
+      if (view.getRootWindow() == null)
+        f.add(new ViewFactory() {
+          public Icon getIcon() {
+            return view.getIcon();
+          }
+
+          public String getTitle() {
+            return view.getTitle();
+          }
+
+          public View createView() {
+            return view;
+          }
+        });
+    }
+
+    return (ViewFactory[]) f.toArray(new ViewFactory[f.size()]);
+  }
+
+  /**
+   * Returns true if this view map contains the view.
+   *
+   * @param view the view
+   * @return true if this view map contains the view
+   * @since IDW 1.3.0
+   */
+  public boolean contains(View view) {
+    return views.contains(view);
+  }
+
+  public void writeView(View view, ObjectOutputStream out) throws IOException {
+    for (Iterator it = viewMap.entrySet().iterator(); it.hasNext();) {
+      Map.Entry entry = (Map.Entry) it.next();
+
+      if (entry.getValue() == view) {
+        writeViewId(entry.getKey(), out);
+        return;
+      }
+    }
+
+    throw new IOException("Serialization of unknown view!");
+  }
+
+  public View readView(ObjectInputStream in) throws IOException {
+    return (View) viewMap.get(readViewId(in));
+  }
+
+  protected void addView(Object id, View view) {
+    Object oldView = viewMap.put(id, view);
+
+    if (oldView != null)
+      views.remove(oldView);
+
+    views.add(view);
+  }
+
+  protected void removeView(Object id) {
+    Object view = viewMap.remove(id);
+
+    if (view != null)
+      views.remove(view);
+  }
+
+  protected View getView(Object id) {
+    return (View) viewMap.get(id);
+  }
+
+}
diff --git a/src/net/infonode/docking/util/DeveloperUtil.java b/src/net/infonode/docking/util/DeveloperUtil.java
new file mode 100644
index 0000000..c750b29
--- /dev/null
+++ b/src/net/infonode/docking/util/DeveloperUtil.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DeveloperUtil.java,v 1.9 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.util;
+
+import net.infonode.docking.*;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * <p>
+ * Utility methods to make certain tasks easier during the development of an application using IDW.
+ * </p>
+ *
+ * <p>
+ * <strong>Note:</strong> These methods might be changed/removed or not be compatible with future versions
+ * of IDW.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ * @since IDW 1.4.0
+ */
+public class DeveloperUtil {
+  private static String INDENT_STRING = "    ";
+
+  /**
+   * <p>
+   * Returns a Java code pseudo-like string with information about the current window layout in a docking
+   * window.
+   * </p>
+   *
+   * <p>
+   * If the given window is a root window a complete layout is returned i.e. windows
+   * inside the root window, windows on window bars and floating windows. This is useful when for
+   * example creating a default layout. Just add all the views to the root window, drag them around
+   * to create a nice layout and the call this function to retrieve the layout as a string.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> The returned string contains pseudo-like Java code. All views in the layout
+   * are called <i>View: "title" - view class</i>.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> The method might be changed/removed or not be compatible with future versions
+   * of IDW.
+   * </p>
+   *
+   * @param window the docking window to retrieve layout for
+   * @return the layout as a pseudo-like Java code
+   */
+  public static String getWindowLayoutAsString(DockingWindow window) {
+    return getDockingWindowLayout(window, 0);
+  }
+
+  /**
+   * <p>
+   * Creates a JFrame with a text area that shows the layout of the given window as pseudo-like Java code,
+   * i.e. the layout retrieved by {@link DeveloperUtil#getWindowLayoutAsString(DockingWindow)}. The frame
+   * also has a button that when clicked gets the current layout from the window.
+   * </p>
+   *
+   * <p>
+   * The frame is useful when designing window layouts in an application. Just create a frame and use your
+   * root window as window. Drag around your views, press the "Get Layout" button and you'll se your layout
+   * in the text area.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> The method might be changed/removed or not be compatible with future versions
+   * of IDW.
+   * </p>
+   *
+   * @param title  frame title
+   * @param window the docking window to retrieve layout for
+   * @return the frame
+   */
+  public static JFrame createWindowLayoutFrame(String title, final DockingWindow window) {
+    JFrame frame = new JFrame(title);
+    frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+
+    final JTextArea layoutArea = new JTextArea(getWindowLayoutAsString(window));
+    JButton getLayoutButton = new JButton("Get Current Layout");
+    getLayoutButton.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        layoutArea.setText(getWindowLayoutAsString(window));
+      }
+    });
+
+    Box box = new Box(BoxLayout.X_AXIS);
+    box.add(getLayoutButton);
+
+    frame.getContentPane().add(new JScrollPane(layoutArea), BorderLayout.CENTER);
+    frame.getContentPane().add(box, BorderLayout.NORTH);
+
+    frame.pack();
+
+    return frame;
+  }
+
+  private static String getDockingWindowLayout(DockingWindow window, int depth) {
+    if (window instanceof RootWindow)
+      return getRootWindowLayout((RootWindow) window, depth);
+
+    String s = depth > 0 ? "\n" : "";
+
+    for (int i = 0; i < depth; i++)
+      s += INDENT_STRING;
+
+    if (window instanceof TabWindow)
+      s += getTabWindowLayout((TabWindow) window, depth + 1);
+    else if (window instanceof SplitWindow)
+      s += getSplitWindowLayout((SplitWindow) window, depth + 1);
+    else
+      s += getViewLayout((View) window, depth + 1);
+
+    return s;
+  }
+
+  private static String getRootWindowLayout(RootWindow window, int depth) {
+    String s = "";
+    if (window.getWindow() != null)
+      s += "<rootWindow>.setWindow(" + getDockingWindowLayout(window.getWindow(), depth) + ");\n\n";
+
+    for (int i = 0; i < window.getChildWindowCount(); i++) {
+      DockingWindow w = window.getChildWindow(i);
+      if (w != window.getWindow()) {
+        if (w instanceof WindowBar) {
+          WindowBar bar = (WindowBar) w;
+
+          if (bar.getChildWindowCount() > 0) {
+            for (int k = 0; k < bar.getChildWindowCount(); k++)
+              s += "<rootWindow>.getWindowBar(Direction." + bar.getDirection().toString().toUpperCase() + ").addTab(" +
+                   getDockingWindowLayout(
+                       bar.getChildWindow(k), depth) + ");\n";
+            s += "\n";
+          }
+        }
+        else if (w instanceof FloatingWindow) {
+          FloatingWindow fw = (FloatingWindow) w;
+          Point loc = fw.getTopLevelAncestor().getLocation();
+          Dimension size = fw.getRootPane().getSize();
+          s += "<rootWindow>.createFloatingWindow(new Point(" + loc.x + ", " + loc.y + "), new Dimension(" + size
+              .width + ", " + size.height + "), ";
+          s += getDockingWindowLayout(fw.getChildWindow(0), depth);
+          s += ");\n\n";
+        }
+      }
+    }
+
+    return s;
+  }
+
+  private static String getTabWindowLayout(TabWindow window, int depth) {
+    if (window.getChildWindowCount() == 1 && window.getChildWindow(0) instanceof View) {
+      return getViewLayout((View) window.getChildWindow(0), depth);
+    }
+
+    String s = "new TabWindow(new DockingWindow[]{";
+
+    for (int i = 0; i < window.getChildWindowCount(); i++) {
+      s += getDockingWindowLayout(window.getChildWindow(i), depth);
+      if (i < window.getChildWindowCount() - 1)
+        s += ", ";
+    }
+
+    s += "})";
+
+    return s;
+  }
+
+  private static String getSplitWindowLayout(SplitWindow window, int depth) {
+    String s = "new SplitWindow(" + window.isHorizontal() + ", " + window.getDividerLocation() + "f, ";
+    s += getDockingWindowLayout(window.getLeftWindow(), depth) + ", ";
+    s += getDockingWindowLayout(window.getRightWindow(), depth);
+    s += ")";
+
+    return s;
+  }
+
+  private static String getViewLayout(View view, int depth) {
+    return "View: \"" + view.getTitle() + "\" - " + view.getClass();
+  }
+}
diff --git a/src/net/infonode/docking/util/DockingUtil.java b/src/net/infonode/docking/util/DockingUtil.java
new file mode 100644
index 0000000..86499a2
--- /dev/null
+++ b/src/net/infonode/docking/util/DockingUtil.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockingUtil.java,v 1.26 2009/02/05 15:57:55 jesper Exp $
+package net.infonode.docking.util;
+
+import net.infonode.docking.*;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+
+/**
+ * Class that contains utility methods for docking windows.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.26 $
+ */
+public final class DockingUtil {
+  private DockingUtil() {
+  }
+
+  /**
+   * Creates a root window with support for view serialization and popup menues.
+   * All the views are added to a tab window which is placed in the root window.
+   *
+   * @param views                 the views that can be shown inside the root window
+   * @param createWindowPopupMenu true if a standard window popup menu should be created
+   * @return the created root window
+   */
+  public static RootWindow createRootWindow(AbstractViewMap views, boolean createWindowPopupMenu) {
+    return createRootWindow(views, views, createWindowPopupMenu);
+  }
+
+  /**
+   * <p>
+   * Creates a root window with support for view serialization, popup menues and support for heavy
+   * weight components inside the views.
+   * </p>
+   *
+   * <p>
+   * All the views are added to a tab window which is placed in the root window.
+   * </p>
+   *
+   * @param views                 the views that can be shown inside the root window
+   * @param createWindowPopupMenu true if a standard window popup menu should be created
+   * @return the created root window
+   * @since IDW 1.4.0
+   */
+  public static RootWindow createHeavyweightSupportedRootWindow(AbstractViewMap views, boolean createWindowPopupMenu) {
+    return createRootWindow(true, views, views, createWindowPopupMenu);
+  }
+
+  /**
+   * Creates a root window with support for view serialization and popup menues.
+   * All the views are added to a tab window which is placed in the root window.
+   *
+   * @param views                 contains all the static views
+   * @param viewSerializer        the view serializer used in the created {@link RootWindow}
+   * @param createWindowPopupMenu true if a standard window popup menu should be created
+   * @return the created root window
+   */
+  public static RootWindow createRootWindow(AbstractViewMap views,
+                                            ViewSerializer viewSerializer,
+                                            boolean createWindowPopupMenu) {
+
+    return createRootWindow(false, views, viewSerializer, createWindowPopupMenu);
+  }
+
+  /**
+   * <p>
+   * Creates a root window with support for view serialization, popup menues and support for
+   * heavyweight components inside the views.
+   * </p>
+   *
+   * <p>
+   * All the views are added to a tab window which is placed in the root window.
+   * </p>
+   *
+   * @param views                 contains all the static views
+   * @param viewSerializer        the view serializer used in the created {@link RootWindow}
+   * @param createWindowPopupMenu true if a standard window popup menu should be created
+   * @return the created root window
+   * @since IDW 1.4.0
+   */
+  public static RootWindow createHeavyweightSupportedRootWindow(AbstractViewMap views,
+                                                                ViewSerializer viewSerializer,
+                                                                boolean createWindowPopupMenu) {
+
+    return createRootWindow(true, views, viewSerializer, createWindowPopupMenu);
+  }
+
+  private static RootWindow createRootWindow(boolean heavyweightSupport,
+                                             AbstractViewMap views,
+                                             ViewSerializer viewSerializer,
+                                             boolean createWindowPopupMenu) {
+    TabWindow tabWindow = new TabWindow();
+
+    for (int i = 0; i < views.getViewCount(); i++)
+      tabWindow.addTab(views.getViewAtIndex(i));
+    tabWindow.setSelectedTab(0);
+    RootWindow rootWindow = new RootWindow(heavyweightSupport, viewSerializer, tabWindow);
+
+    if (createWindowPopupMenu)
+      rootWindow.setPopupMenuFactory(WindowMenuUtil.createWindowMenuFactory(views, true));
+
+    return rootWindow;
+  }
+
+  /**
+   * Returns true if <tt>ancestor</tt> is an ancestor of <tt>child</tt> or the windows are the same.
+   *
+   * @param ancestor the ancestor window
+   * @param child    the child window
+   * @return true if <tt>ancestor</tt> is an ancestor of <tt>child</tt> or the windows are the same
+   */
+  public static boolean isAncestor(DockingWindow ancestor, DockingWindow child) {
+    return child != null && (ancestor == child || isAncestor(ancestor, child.getWindowParent()));
+  }
+
+  /**
+   * <p>
+   * Adds a window inside a root window. The following methods are tried in order:
+   * </p>
+   * <ol>
+   * <li>If the window already is added inside the root window nothing happens.</li>
+   * <li>The window is restored to it's last location if that location is inside the root window.</li>
+   * <li>The window is added inside the root window.</li>
+   * </ol>
+   *
+   * @param window     the window to add
+   * @param rootWindow the root window in which to add it
+   * @since IDW 1.1.0
+   */
+  public static void addWindow(DockingWindow window, RootWindow rootWindow) {
+    if (rootWindow == null || window.getRootWindow() == rootWindow)
+      return;
+
+    if (window.getRootWindow() == null) {
+      window.restore();
+
+      if (window.getRootWindow() == rootWindow)
+        return;
+    }
+
+    InternalDockingUtil.addToRootWindow(window, rootWindow);
+  }
+
+  /**
+   * Returns the {@link TabWindow} for a window. This is either the window itself or the parent window.
+   *
+   * @param window the window
+   * @return the {@link TabWindow} for the window
+   * @since IDW 1.3.0
+   */
+  public static TabWindow getTabWindowFor(DockingWindow window) {
+    return window instanceof TabWindow ? (TabWindow) window :
+      window.getWindowParent() != null && window.getWindowParent() instanceof TabWindow ?
+                                                                                         (TabWindow) window.getWindowParent() :
+                                                                                           null;
+  }
+
+  /**
+   * Returns the {@link FloatingWindow} for a window if the window is undocked.
+   *
+   * @param window the window
+   * @return the {@link FloatingWindow} for the window or null if the window is not undocked
+   * @since IDW 1.4.0
+   */
+  public static FloatingWindow getFloatingWindowFor(DockingWindow window) {
+    if (window == null)
+      return null;
+
+    if (!window.isUndocked())
+      return null;
+
+    while (window != null && !(window instanceof FloatingWindow))
+      window = window.getWindowParent();
+
+    return (FloatingWindow) window;
+  }
+}
diff --git a/src/net/infonode/docking/util/MixedViewHandler.java b/src/net/infonode/docking/util/MixedViewHandler.java
new file mode 100644
index 0000000..9b213aa
--- /dev/null
+++ b/src/net/infonode/docking/util/MixedViewHandler.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: MixedViewHandler.java,v 1.4 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.util;
+
+import net.infonode.docking.View;
+import net.infonode.docking.ViewSerializer;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * The mixed view map simplifies mixing static and dynamic views inside the same root window.
+ * The static views are handled by an {@link AbstractViewMap} and the dynamic views are handled
+ * by an custom {@link ViewSerializer}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.3.0
+ */
+public class MixedViewHandler implements ViewFactoryManager, ViewSerializer {
+  private AbstractViewMap viewMap;
+  private ViewSerializer viewSerializer;
+
+  /**
+   * Constructor.
+   *
+   * @param viewMap        this map is first searched when serializing a view
+   * @param viewSerializer is used if the view was not found in the <tt>viewMap</tt>
+   */
+  public MixedViewHandler(AbstractViewMap viewMap, ViewSerializer viewSerializer) {
+    this.viewMap = viewMap;
+    this.viewSerializer = viewSerializer;
+  }
+
+  public ViewFactory[] getViewFactories() {
+    return new ViewFactory[0];
+  }
+
+  public void writeView(View view, ObjectOutputStream out) throws IOException {
+    if (viewMap.contains(view)) {
+      out.writeBoolean(true);
+      viewMap.writeView(view, out);
+    }
+    else {
+      out.writeBoolean(false);
+      viewSerializer.writeView(view, out);
+    }
+  }
+
+  public View readView(ObjectInputStream in) throws IOException {
+    return in.readBoolean() ? viewMap.readView(in) : viewSerializer.readView(in);
+  }
+
+}
diff --git a/src/net/infonode/docking/util/PropertiesUtil.java b/src/net/infonode/docking/util/PropertiesUtil.java
new file mode 100644
index 0000000..a1a7471
--- /dev/null
+++ b/src/net/infonode/docking/util/PropertiesUtil.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertiesUtil.java,v 1.16 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.docking.util;
+
+import net.infonode.docking.properties.RootWindowProperties;
+import net.infonode.tabbedpanel.TabAreaVisiblePolicy;
+import net.infonode.tabbedpanel.TabLayoutPolicy;
+import net.infonode.util.Direction;
+
+/**
+ * Utility functions for manipulating properties.
+ *
+ * @author johan
+ * @since IDW 1.4.0
+ */
+public class PropertiesUtil {
+  private PropertiesUtil() {
+  }
+
+  /**
+   * <p>
+   * Creates and returns a new {@link RootWindowProperties} object that is meant to be
+   * added as super object on another {@link RootWindowProperties} object, for example
+   * a theme's {@link RootWindowProperties}.
+   * </p>
+   *
+   * <p>
+   * The created properties object will have title bar style properties set, see
+   * {@link #setTitleBarStyle(RootWindowProperties)}.
+   * </p>
+   *
+   * @return created properties object
+   */
+  public static RootWindowProperties createTitleBarStyleRootWindowProperties() {
+    RootWindowProperties titleBarStyleProperties = new RootWindowProperties();
+    setupTitleBarStyleProperties(titleBarStyleProperties);
+    return titleBarStyleProperties;
+  }
+
+  /**
+   * <p>
+   * Sets title bar style in the given root window properties object.
+   * </p>
+   *
+   * <p>
+   * This function sets properties in the give {@link RootWindowProperties} object:
+   * </p>
+   * <ul>
+   * <li>View title bars are made visible at the top of a view.</li>
+   * <li>Tab area is oriented below the content area.</li>
+   * <li>No tab buttons are visible (except custom buttons).</li>
+   * <li>No tab window buttons except scroll buttons, drop down list button
+   * and custom buttons.</li>
+   * <li>Hide the entire tab area when a tab window contains a view as the
+   * only child.</li>
+   * </ul>
+   *
+   * <p>
+   * <strong>Note:</strong> It will modify properties values in the object
+   * without checking if it was already set i.e. overwriting the previous
+   * value.
+   * </p>
+   *
+   * @param rootProps {@link RootWindowProperties} object to modify
+   */
+  public static void setTitleBarStyle(RootWindowProperties rootProps) {
+    setupTitleBarStyleProperties(rootProps);
+  }
+
+  private static void setupTitleBarStyleProperties(RootWindowProperties titleBarStyleProperties) {
+    titleBarStyleProperties.getViewProperties().getViewTitleBarProperties().setVisible(true);
+    titleBarStyleProperties.getTabWindowProperties().getTabbedPanelProperties().setTabAreaOrientation(Direction.DOWN)
+        .setTabLayoutPolicy(TabLayoutPolicy.SCROLLING);
+    titleBarStyleProperties.getTabWindowProperties().getTabbedPanelProperties().getTabAreaProperties()
+        .setTabAreaVisiblePolicy(TabAreaVisiblePolicy.MORE_THAN_ONE_TAB);
+
+    titleBarStyleProperties.getTabWindowProperties().getTabProperties().getHighlightedButtonProperties()
+        .getMinimizeButtonProperties()
+        .setVisible(false);
+    titleBarStyleProperties.getTabWindowProperties().getTabProperties().getHighlightedButtonProperties()
+        .getRestoreButtonProperties()
+        .setVisible(false);
+    titleBarStyleProperties.getTabWindowProperties().getTabProperties().getHighlightedButtonProperties()
+        .getCloseButtonProperties()
+        .setVisible(false);
+    titleBarStyleProperties.getTabWindowProperties().getTabProperties().getHighlightedButtonProperties()
+        .getUndockButtonProperties()
+        .setVisible(false);
+    titleBarStyleProperties.getTabWindowProperties().getTabProperties().getHighlightedButtonProperties()
+        .getDockButtonProperties()
+        .setVisible(false);
+
+    titleBarStyleProperties.getTabWindowProperties().getCloseButtonProperties().setVisible(false);
+    titleBarStyleProperties.getTabWindowProperties().getMaximizeButtonProperties().setVisible(false);
+    titleBarStyleProperties.getTabWindowProperties().getMinimizeButtonProperties().setVisible(false);
+    titleBarStyleProperties.getTabWindowProperties().getUndockButtonProperties().setVisible(false);
+    titleBarStyleProperties.getTabWindowProperties().getDockButtonProperties().setVisible(false);
+    titleBarStyleProperties.getTabWindowProperties().getRestoreButtonProperties().setVisible(false);
+
+    titleBarStyleProperties.getWindowBarProperties().getTabWindowProperties().getTabProperties()
+        .getHighlightedButtonProperties()
+        .getMinimizeButtonProperties()
+        .setVisible(true);
+    titleBarStyleProperties.getWindowBarProperties().getTabWindowProperties().getTabProperties()
+        .getHighlightedButtonProperties()
+        .getRestoreButtonProperties()
+        .setVisible(true);
+    titleBarStyleProperties.getWindowBarProperties().getTabWindowProperties().getTabProperties()
+        .getHighlightedButtonProperties()
+        .getCloseButtonProperties()
+        .setVisible(true);
+    titleBarStyleProperties.getWindowBarProperties().getTabWindowProperties().getTabProperties()
+        .getHighlightedButtonProperties()
+        .getUndockButtonProperties()
+        .setVisible(true);
+    titleBarStyleProperties.getWindowBarProperties().getTabWindowProperties().getTabProperties()
+        .getHighlightedButtonProperties()
+        .getDockButtonProperties()
+        .setVisible(true);
+
+    titleBarStyleProperties.getWindowBarProperties().getTabWindowProperties().getTabbedPanelProperties()
+        .setTabLayoutPolicy(TabLayoutPolicy.SCROLLING);
+  }
+}
diff --git a/src/net/infonode/docking/util/StringViewMap.java b/src/net/infonode/docking/util/StringViewMap.java
new file mode 100644
index 0000000..0d6c10e
--- /dev/null
+++ b/src/net/infonode/docking/util/StringViewMap.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: StringViewMap.java,v 1.7 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.util;
+
+import net.infonode.docking.View;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * A map of views that handles view serialization by assigning a string id to each view.
+ * The id is unique for each view in the map. To guarantee serialization compatibility a view id must remain constant.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ * @since IDW 1.1.0
+ */
+public class StringViewMap extends AbstractViewMap {
+  /**
+   * Constructor.
+   */
+  public StringViewMap() {
+  }
+
+  /**
+   * Utility constructor that creates a map with a number of views.
+   * A view gets it's title as id.
+   *
+   * @param views the views to add to the map
+   */
+  public StringViewMap(View[] views) {
+    for (int i = 0; i < views.length; i++)
+      addView(views[i]);
+  }
+
+  /**
+   * Adds a view to the map.
+   * The view title is used as id.
+   *
+   * @param view the view
+   */
+  public void addView(View view) {
+    addView(view.getTitle(), view);
+  }
+
+  /**
+   * Adds a view to the map.
+   *
+   * @param id   the view id
+   * @param view the view
+   */
+  public void addView(String id, View view) {
+    addView((Object) id, view);
+  }
+
+  /**
+   * Removes a view with a specific id from the map.
+   *
+   * @param id the view id
+   */
+  public void removeView(String id) {
+    removeView((Object) id);
+  }
+
+  /**
+   * Returns the view with a specific id.
+   *
+   * @param id the view id
+   * @return the view with the id
+   */
+  public View getView(String id) {
+    return getView((Object) id);
+  }
+
+  protected void writeViewId(Object id, ObjectOutputStream out) throws IOException {
+    out.writeUTF((String) id);
+  }
+
+  protected Object readViewId(ObjectInputStream in) throws IOException {
+    return in.readUTF();
+  }
+}
diff --git a/src/net/infonode/docking/util/ViewFactory.java b/src/net/infonode/docking/util/ViewFactory.java
new file mode 100644
index 0000000..f12ba6b
--- /dev/null
+++ b/src/net/infonode/docking/util/ViewFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ViewFactory.java,v 1.5 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.util;
+
+import net.infonode.docking.View;
+
+import javax.swing.*;
+
+/**
+ * A factory that creates a view.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public interface ViewFactory {
+  /**
+   * Returns the icon for this factory.
+   *
+   * @return the icon for this factory
+   */
+  Icon getIcon();
+
+  /**
+   * Returns the title of this factory.
+   *
+   * @return the title of this factory
+   */
+  String getTitle();
+
+  /**
+   * Creates a view.
+   *
+   * @return the view
+   */
+  View createView();
+}
diff --git a/src/net/infonode/docking/util/ViewFactoryManager.java b/src/net/infonode/docking/util/ViewFactoryManager.java
new file mode 100644
index 0000000..1dd5bea
--- /dev/null
+++ b/src/net/infonode/docking/util/ViewFactoryManager.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ViewFactoryManager.java,v 1.2 2004/09/22 14:31:39 jesper Exp $
+package net.infonode.docking.util;
+
+/**
+ * Manages the factories for views.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ */
+public interface ViewFactoryManager {
+  /**
+   * Returns the view factories.
+   *
+   * @return the view factories
+   */
+  ViewFactory[] getViewFactories();
+}
diff --git a/src/net/infonode/docking/util/ViewMap.java b/src/net/infonode/docking/util/ViewMap.java
new file mode 100644
index 0000000..3006004
--- /dev/null
+++ b/src/net/infonode/docking/util/ViewMap.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ViewMap.java,v 1.7 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.docking.util;
+
+import net.infonode.docking.View;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * A map of views that handles view serialization by assigning an integer id to each view.
+ * The id is unique for each view in the map. To guarantee serialization compatibility a view id must remain constant.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class ViewMap extends AbstractViewMap {
+  /**
+   * Constructor.
+   */
+  public ViewMap() {
+  }
+
+  /**
+   * Utility constructor that creates a map with a number of views.
+   * A view gets it's index in the array as id.
+   *
+   * @param views the views to add to the map
+   */
+  public ViewMap(View[] views) {
+    for (int i = 0; i < views.length; i++)
+      addView(i, views[i]);
+  }
+
+  /**
+   * Adds a view to the map.
+   *
+   * @param id   the view id
+   * @param view the view
+   */
+  public void addView(int id, View view) {
+    addView(new Integer(id), view);
+  }
+
+  /**
+   * Removes a view with a specific id from the map.
+   *
+   * @param id the view id
+   */
+  public void removeView(int id) {
+    removeView(new Integer(id));
+  }
+
+  /**
+   * Returns the view with a specific id.
+   *
+   * @param id the view id
+   * @return the view with the id
+   */
+  public View getView(int id) {
+    return getView(new Integer(id));
+  }
+
+  protected void writeViewId(Object id, ObjectOutputStream out) throws IOException {
+    out.writeInt(((Integer) id).intValue());
+  }
+
+  protected Object readViewId(ObjectInputStream in) throws IOException {
+    return new Integer(in.readInt());
+  }
+
+
+}
diff --git a/src/net/infonode/docking/util/WindowMenuUtil.java b/src/net/infonode/docking/util/WindowMenuUtil.java
new file mode 100644
index 0000000..fddb6c4
--- /dev/null
+++ b/src/net/infonode/docking/util/WindowMenuUtil.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowMenuUtil.java,v 1.27 2007/01/28 21:25:10 jesper Exp $
+package net.infonode.docking.util;
+
+import net.infonode.docking.*;
+import net.infonode.docking.action.*;
+import net.infonode.docking.internalutil.InternalDockingUtil;
+import net.infonode.gui.icon.button.ArrowIcon;
+import net.infonode.gui.menu.MenuUtil;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Class containing utility methods for creating window popup menues.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.27 $
+ */
+public final class WindowMenuUtil {
+  private WindowMenuUtil() {
+  }
+
+  private static final Icon[] ARROW_ICONS = new Icon[4];
+
+  static {
+    final Direction[] directions = Direction.getDirections();
+
+    for (int i = 0; i < directions.length; i++)
+      ARROW_ICONS[i] = new ArrowIcon(InternalDockingUtil.DEFAULT_BUTTON_ICON_SIZE + 1, directions[i]);
+  }
+
+  private static AbstractTabWindow getTabWindowFor(DockingWindow window) {
+    return (AbstractTabWindow)
+        (window instanceof AbstractTabWindow ? window :
+         window.getWindowParent() != null && window.getWindowParent() instanceof AbstractTabWindow ?
+         window.getWindowParent() :
+         null);
+  }
+
+  private static JMenu getMoveToMenuItems(final DockingWindow window) {
+    JMenu moveToMenu = new JMenu("Move to Window Bar");
+
+    if (window.isMinimizable()) {
+      final RootWindow root = window.getRootWindow();
+      final Direction[] directions = Direction.getDirections();
+
+      for (int i = 0; i < 4; i++) {
+        final Direction dir = directions[i];
+
+        if (!DockingUtil.isAncestor(root.getWindowBar(dir), window) && root.getWindowBar(dir).isEnabled()) {
+          moveToMenu.add(new JMenuItem(dir.getName(), ARROW_ICONS[i])).addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+              root.getWindowBar(dir).addTab(window);
+            }
+          });
+        }
+      }
+    }
+
+    return moveToMenu;
+  }
+
+  private static void addWindowMenuItems(JPopupMenu menu, DockingWindow window) {
+    menu.add(UndockWithAbortWindowAction.INSTANCE.getAction(window).toSwingAction());
+    menu.add(DockWithAbortWindowAction.INSTANCE.getAction(window).toSwingAction());
+    menu.add(RestoreWithAbortWindowAction.INSTANCE.getAction(window).toSwingAction());
+    menu.add(MinimizeWithAbortWindowAction.INSTANCE.getAction(window).toSwingAction());
+
+    if (window instanceof TabWindow)
+      menu.add(MaximizeWithAbortWindowAction.INSTANCE.getAction(window).toSwingAction());
+
+    menu.add(CloseWithAbortWindowAction.INSTANCE.getAction(window).toSwingAction());
+
+    if (window.getWindowParent() instanceof AbstractTabWindow)
+      menu.add(CloseOthersWindowAction.INSTANCE.getAction(window).toSwingAction());
+
+    JMenu moveToMenu = getMoveToMenuItems(window);
+
+    if (moveToMenu.getItemCount() > 0) {
+      menu.add(moveToMenu);
+    }
+  }
+
+  private static void addNewViewMenuItems(JPopupMenu menu, final DockingWindow window, ViewFactoryManager viewManager) {
+    ViewFactory[] viewFactories = viewManager.getViewFactories();
+
+    if (viewFactories.length == 0)
+      return;
+
+    JMenu viewsPopup = new JMenu("Show View");
+
+    for (int i = 0; i < viewFactories.length; i++) {
+      final ViewFactory vf = viewFactories[i];
+
+      viewsPopup.add(new JMenuItem(vf.getTitle(), vf.getIcon())).addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          View view = vf.createView();
+
+          if (view.getRootWindow() == window.getRootWindow())
+            return;
+
+          view.restore();
+
+          if (view.getRootWindow() == window.getRootWindow())
+            return;
+
+          if (window instanceof RootWindow)
+            ((RootWindow) window).setWindow(view);
+          else {
+            AbstractTabWindow tabWindow = getTabWindowFor(window);
+
+            if (tabWindow != null)
+              tabWindow.addTab(view);
+          }
+        }
+      });
+    }
+
+    menu.add(viewsPopup);
+  }
+
+  private static void addTabOrientationMenuItems(JPopupMenu menu, DockingWindow window) {
+    final AbstractTabWindow tabWindow = getTabWindowFor(window);
+
+    if (tabWindow == null || tabWindow instanceof WindowBar)
+      return;
+
+    JMenu orientationMenu = new JMenu("Tab Orientation");
+    TabbedPanelProperties properties = tabWindow.getTabWindowProperties().getTabbedPanelProperties();
+    final Direction[] directions = Direction.getDirections();
+
+    for (int i = 0; i < directions.length; i++) {
+      final Direction dir = directions[i];
+      JMenuItem item = orientationMenu.add(new JMenuItem(dir.getName(), ARROW_ICONS[i]));
+      item.setEnabled(dir != properties.getTabAreaOrientation());
+      item.addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          tabWindow.getTabWindowProperties().getTabbedPanelProperties().setTabAreaOrientation(dir);
+        }
+      });
+    }
+
+    menu.add(orientationMenu);
+  }
+
+  private static void addTabDirectionMenuItems(JPopupMenu menu, DockingWindow window) {
+    final AbstractTabWindow tabWindow = getTabWindowFor(window);
+
+    if (tabWindow == null)
+      return;
+
+    JMenu directionMenu = new JMenu("Tab Direction");
+    TitledTabProperties properties = TitledTabProperties.getDefaultProperties();
+    properties.addSuperObject(tabWindow.getTabWindowProperties().getTabProperties().getTitledTabProperties());
+    final Direction[] directions = Direction.getDirections();
+
+    for (int i = 0; i < directions.length; i++) {
+      final Direction dir = directions[i];
+
+      if (dir != Direction.LEFT) {
+        JMenuItem item = directionMenu.add(new JMenuItem(dir.getName(), ARROW_ICONS[i]));
+        item.setEnabled(dir != properties.getNormalProperties().getDirection());
+        item.addActionListener(new ActionListener() {
+          public void actionPerformed(ActionEvent e) {
+            tabWindow.getTabWindowProperties().getTabProperties().getTitledTabProperties().getNormalProperties()
+                .setDirection(dir);
+          }
+        });
+      }
+    }
+
+    menu.add(directionMenu);
+  }
+
+  private static void addSplitWindowMenuItems(JPopupMenu menu, final DockingWindow window) {
+    if (window instanceof SplitWindow) {
+      JMenu splitMenu = new JMenu("Split Window");
+
+      splitMenu.add("25%").addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          ((SplitWindow) window).setDividerLocation(0.25f);
+        }
+      });
+
+      splitMenu.add("50%").addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          ((SplitWindow) window).setDividerLocation(0.5f);
+        }
+      });
+
+      splitMenu.add("75%").addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          ((SplitWindow) window).setDividerLocation(0.75f);
+        }
+      });
+
+      splitMenu.addSeparator();
+
+      splitMenu.add("Flip Orientation").addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          ((SplitWindow) window).setHorizontal(!((SplitWindow) window).isHorizontal());
+        }
+      });
+
+      splitMenu.add("Mirror").addActionListener(new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          SplitWindow sw = (SplitWindow) window;
+          sw.setWindows(window.getChildWindow(1), window.getChildWindow(0));
+          sw.setDividerLocation(1 - sw.getDividerLocation());
+        }
+      });
+
+      menu.add(splitMenu);
+    }
+  }
+
+  /**
+   * Creates a factory which creates a popup menu containing common window actions.
+   *
+   * @param viewFactoryManager used for creating a list of views that the user can show
+   * @param addTabItems        add items for changing tab direction and orientation
+   * @return the window popup menu factory
+   */
+  public static WindowPopupMenuFactory createWindowMenuFactory(ViewFactoryManager viewFactoryManager,
+                                                               boolean addTabItems) {
+    return createWindowMenuFactory(viewFactoryManager, addTabItems, true);
+  }
+
+  /**
+   * Creates a factory which creates a popup menu containing common window actions.
+   *
+   * @param viewFactoryManager  used for creating a list of views that the user can show
+   * @param addTabItems         add items for changing tab direction and orientation
+   * @param addSplitWindowItems add items for {@link SplitWindow}'s
+   * @return the window popup menu factory
+   * @since IDW 1.2.0
+   */
+  public static WindowPopupMenuFactory createWindowMenuFactory(final ViewFactoryManager viewFactoryManager,
+                                                               final boolean addTabItems,
+                                                               final boolean addSplitWindowItems) {
+    return new WindowPopupMenuFactory() {
+      public JPopupMenu createPopupMenu(DockingWindow window) {
+        JPopupMenu menu = new JPopupMenu(window.getTitle());
+
+        if (!(window instanceof RootWindow)) {
+          if (!(window instanceof WindowBar)) {
+            addWindowMenuItems(menu, window);
+            menu.addSeparator();
+          }
+
+          if (addTabItems) {
+            addTabOrientationMenuItems(menu, window);
+            addTabDirectionMenuItems(menu, window);
+            menu.addSeparator();
+          }
+
+          if (addSplitWindowItems) {
+            addSplitWindowMenuItems(menu, window);
+            menu.addSeparator();
+          }
+        }
+
+        addNewViewMenuItems(menu, window, viewFactoryManager);
+        MenuUtil.optimizeSeparators(menu);
+        MenuUtil.align(menu);
+        return menu;
+      }
+    };
+  }
+
+}
diff --git a/src/net/infonode/docking/util/package.html b/src/net/infonode/docking/util/package.html
new file mode 100644
index 0000000..ffb6798
--- /dev/null
+++ b/src/net/infonode/docking/util/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Utility classes for docking windows.
+</body>
+</html>
diff --git a/src/net/infonode/gui/BackgroundPainter.java b/src/net/infonode/gui/BackgroundPainter.java
new file mode 100644
index 0000000..551fe6f
--- /dev/null
+++ b/src/net/infonode/gui/BackgroundPainter.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BackgroundPainter.java,v 1.4 2004/11/11 13:07:28 jesper Exp $
+package net.infonode.gui;
+
+import net.infonode.gui.componentpainter.ComponentPainter;
+
+/**
+ * An object that paints its background using a {@link ComponentPainter}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.2.0
+ */
+public interface BackgroundPainter {
+  /**
+   * Returns the {@link ComponentPainter} that is used to paint the background of this object.
+   *
+   * @return the {@link ComponentPainter} that is used to paint the background of this object, null if there is none
+   */
+  ComponentPainter getComponentPainter();
+}
diff --git a/src/net/infonode/gui/ButtonFactory.java b/src/net/infonode/gui/ButtonFactory.java
new file mode 100644
index 0000000..275356e
--- /dev/null
+++ b/src/net/infonode/gui/ButtonFactory.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ButtonFactory.java,v 1.23 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+import net.infonode.gui.border.HighlightBorder;
+import net.infonode.util.ColorUtil;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.LineBorder;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import javax.swing.plaf.ButtonUI;
+import java.awt.*;
+import java.awt.event.*;
+import java.net.URL;
+
+public class ButtonFactory {
+  private ButtonFactory() {
+  }
+
+  private static class ButtonHighlighter implements ComponentListener, HierarchyListener {
+/*    private static final Color HIGHLIGHTED_COLOR = new Color(140, 160, 255);
+    private static final Color PRESSED_COLOR = new Color(60, 80, 200);
+*/
+    private JButton button;
+    private Border pressedBorder;
+    private Border highlightedBorder;
+    private Border normalBorder;
+    private boolean rollover;
+    private long rolloverStart; // Ugly hack to avoid false rollover callbacks which occur when the button is moved 
+
+    ButtonHighlighter(JButton button, int padding) {
+      this.button = button;
+
+      normalBorder = new EmptyBorder(padding + 2, padding + 2, padding + 2, padding + 2);
+      pressedBorder = new EmptyBorder(padding + 2, padding + 2, padding, padding);
+      highlightedBorder = new EmptyBorder(padding + 1, padding + 1, padding + 1, padding + 1);
+
+      button.setContentAreaFilled(false);
+      setNormalState();
+
+      button.addChangeListener(new ChangeListener() {
+        public void stateChanged(ChangeEvent e) {
+          rollover = (System.currentTimeMillis() - rolloverStart) > 20 &&
+                     ButtonHighlighter.this.button.getModel().isRollover();
+          update();
+
+          if (ButtonHighlighter.this.button.getModel().isRollover())
+            rolloverStart = 0;
+        }
+      });
+
+      button.addHierarchyListener(this);
+      button.addComponentListener(this);
+    }
+
+    private void setNormalState() {
+      button.setBackground(null);
+      button.setOpaque(false);
+      button.setBorder(normalBorder);
+      rollover = false;
+    }
+
+    public void componentHidden(ComponentEvent e) {
+      setNormalState();
+      rolloverStart = System.currentTimeMillis();
+    }
+
+    public void componentMoved(ComponentEvent e) {
+      setNormalState();
+      rolloverStart = System.currentTimeMillis();
+    }
+
+    public void componentResized(ComponentEvent e) {
+      setNormalState();
+      rolloverStart = System.currentTimeMillis();
+    }
+
+    public void componentShown(ComponentEvent e) {
+      setNormalState();
+      rolloverStart = System.currentTimeMillis();
+    }
+
+    public void hierarchyChanged(HierarchyEvent e) {
+      setNormalState();
+      rolloverStart = System.currentTimeMillis();
+    }
+
+    private void update() {
+      boolean pressed = button.getModel().isArmed();
+
+      if (button.isEnabled() && (rollover || pressed)) {
+        button.setOpaque(true);
+        Color backgroundColor = ComponentUtil.getBackgroundColor(button.getParent());
+        backgroundColor = backgroundColor == null ?
+                          UIManagerUtil.getColor("control", Color.LIGHT_GRAY) : backgroundColor;
+        button.setBackground(ColorUtil.mult(backgroundColor, pressed ? 0.8 : 1.15));
+
+        button.setBorder(pressed ?
+                         new CompoundBorder(new LineBorder(ColorUtil.mult(backgroundColor, 0.3)),
+                                            pressedBorder) :
+                         new CompoundBorder(new LineBorder(ColorUtil.mult(backgroundColor, 0.5)),
+                                            highlightedBorder));
+      }
+      else {
+        setNormalState();
+      }
+    }
+
+  }
+
+  private static final Border normalBorder = new CompoundBorder(new LineBorder(new Color(70, 70, 70)),
+                                                                new CompoundBorder(new HighlightBorder(),
+                                                                                   new EmptyBorder(1, 6, 1, 6)));
+  private static final Border pressedBorder = new CompoundBorder(new LineBorder(new Color(70, 70, 70)),
+                                                                 new CompoundBorder(new HighlightBorder(true),
+                                                                                    new EmptyBorder(2, 7, 0, 5)));
+
+  private static JButton initButton(final JButton button) {
+    button.setMargin(null);
+    button.setBorder(normalBorder);
+    button.addMouseListener(new MouseAdapter() {
+      public void mousePressed(MouseEvent e) {
+        button.setBorder(pressedBorder);
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        button.setBorder(normalBorder);
+      }
+    });
+
+    return button;
+  }
+
+  private static JButton newButton(String text) {
+    return initButton(new JButton(text));
+  }
+
+  private static JButton newButton(Icon icon) {
+    return initButton(new JButton(icon));
+  }
+
+  private static JButton newButton(Icon icon, String text) {
+    return initButton(new JButton(text, icon));
+  }
+
+  public static final JButton createDialogButton(String text, ActionListener action) {
+    JButton b = new JButton(text);
+    b.setFont(b.getFont().deriveFont(Font.BOLD));
+    b.addActionListener(action);
+    return b;
+  }
+
+  public static final JButton createButton(String text, ActionListener action) {
+    return createButton(text, true, action);
+  }
+
+  public static final JButton createButton(String text, boolean opaque, ActionListener action) {
+    JButton b = newButton(text);
+    b.setOpaque(opaque);
+    b.addActionListener(action);
+    return b;
+  }
+
+  public static final JButton createButton(String iconResource, String text, ActionListener action) {
+    URL iconURL = ButtonFactory.class.getClassLoader().getResource(iconResource);
+    return createButton(iconURL == null ? null : new ImageIcon(iconURL), text, action);
+  }
+
+  public static final JButton createButton(Icon icon, String text, ActionListener action) {
+    JButton b;
+
+    if (icon != null) {
+      b = newButton(icon);
+      b.setToolTipText(text);
+    }
+    else {
+      b = newButton(text);
+    }
+
+    b.addActionListener(action);
+    return b;
+  }
+
+  public static final JButton createButton(Icon icon, String tooltipText, boolean opaque, ActionListener action) {
+    JButton b = newButton(icon);
+    b.setToolTipText(tooltipText);
+    b.addActionListener(action);
+    b.setOpaque(opaque);
+    return b;
+  }
+
+  public static final JButton createFlatHighlightButton(Icon icon, String tooltipText, int padding,
+                                                        ActionListener action) {
+    final JButton b = new JButton(icon) {
+      public void setUI(ButtonUI ui) {
+        super.setUI(new FlatIconButtonUI());
+      }
+    };
+    b.setVerticalAlignment(SwingConstants.CENTER);
+    b.setToolTipText(tooltipText);
+    b.setMargin(new Insets(0, 0, 0, 0));
+    new ButtonHighlighter(b, padding);
+
+    b.setRolloverEnabled(true);
+
+    if (action != null)
+      b.addActionListener(action);
+
+    return b;
+  }
+
+  public static final void applyButtonHighlighter(JButton b, int padding) {
+    b.setVerticalAlignment(SwingConstants.CENTER);
+    b.setMargin(new Insets(0, 0, 0, 0));
+    new ButtonHighlighter(b, padding);
+
+    b.setRolloverEnabled(true);
+  }
+
+  public static final JButton createFlatHighlightButton(Icon icon, String tooltipText, int padding,
+                                                        boolean focusable, ActionListener action) {
+    final JButton b = createFlatHighlightButton(icon, tooltipText, padding, action);
+    b.setFocusable(focusable);
+    return b;
+  }
+
+  public static final JButton createHighlightButton(String text, ActionListener action) {
+    JButton b = newButton(text);
+    b.addActionListener(action);
+    return b;
+  }
+
+  public static final JButton createHighlightButton(Icon icon, ActionListener action) {
+    JButton b = newButton(icon);
+    b.addActionListener(action);
+    return b;
+  }
+
+  public static final JButton createHighlightButton(Icon icon, String text, ActionListener action) {
+    JButton b = newButton(icon, text);
+    b.addActionListener(action);
+    return b;
+  }
+
+  public static final JButton createFlatIconHoverButton(Icon icon, Icon hovered, Icon pressed) {
+    final JButton b = new JButton(icon) {
+      public void setUI(ButtonUI ui) {
+        super.setUI(new FlatIconButtonUI());
+      }
+    };
+    b.setPressedIcon(pressed);
+    b.setRolloverEnabled(true);
+    b.setRolloverIcon(hovered);
+    b.setVerticalAlignment(SwingConstants.CENTER);
+    return b;
+  }
+}
diff --git a/src/net/infonode/gui/Colors.java b/src/net/infonode/gui/Colors.java
new file mode 100644
index 0000000..233a861
--- /dev/null
+++ b/src/net/infonode/gui/Colors.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Colors.java,v 1.4 2004/10/29 15:57:47 jesper Exp $
+package net.infonode.gui;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class Colors {
+  public static final float RED_HUE = 0f;
+  public static final float BROWN_HUE = 0.0833f;
+  public static final float COPPER_HUE = 0.09f;
+  public static final float ORANGE_HUE = 0.1055f;
+  public static final float YELLOW_HUE = 1f / 6;
+  public static final float SAND_HUE = 0.12f;
+  public static final float GREEN_HUE = 1f / 3;
+  public static final float CYAN_HUE = 0.5f;
+  public static final float AZURE_BLUE_HUE = 0.58f;
+  public static final float ROYAL_BLUE_HUE = 0.61f;
+  public static final float BLUE_HUE = 2f / 3;
+  public static final float PURPLE_BUE = 0.77777f;
+  public static final float MAGENTA_HUE = 0.83f;
+
+  private Colors() {
+  }
+}
diff --git a/src/net/infonode/gui/ComponentPaintChecker.java b/src/net/infonode/gui/ComponentPaintChecker.java
new file mode 100644
index 0000000..35e80a1
--- /dev/null
+++ b/src/net/infonode/gui/ComponentPaintChecker.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ComponentPaintChecker.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+
+/**
+ * @author johan
+ */
+public class ComponentPaintChecker {
+  private boolean okToPaint = false;
+
+  public ComponentPaintChecker(final Component c) {
+    c.addHierarchyListener(new HierarchyListener() {
+      public void hierarchyChanged(HierarchyEvent e) {
+        if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) == HierarchyEvent.SHOWING_CHANGED) {
+          if (c.isDisplayable())
+            SwingUtilities.invokeLater(new Runnable() {
+              public void run() {
+                okToPaint = true;
+              }
+            });
+          else
+            okToPaint = false;
+        }
+      }
+    });
+  }
+
+  public boolean isPaintingOk() {
+    return okToPaint;
+  }
+}
diff --git a/src/net/infonode/gui/ComponentUtil.java b/src/net/infonode/gui/ComponentUtil.java
new file mode 100644
index 0000000..efcaf14
--- /dev/null
+++ b/src/net/infonode/gui/ComponentUtil.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ComponentUtil.java,v 1.25 2005/12/04 13:46:04 jesper Exp $
+
+package net.infonode.gui;
+
+import net.infonode.gui.componentpainter.ComponentPainter;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.applet.Applet;
+import java.awt.*;
+import java.util.ArrayList;
+
+public class ComponentUtil {
+  private ComponentUtil() {
+  }
+
+  public static final Component getChildAt(Container container, Point p) {
+    Component c = container.getComponentAt(p);
+    return c == null || c.getParent() != container ? null : c;
+  }
+
+  public static final Component getVisibleChildAt(Container container, Point p) {
+    for (int i = 0; i < container.getComponentCount(); i++) {
+      Component c = container.getComponent(i);
+      if (c.isVisible() && c.contains(p.x - c.getX(), p.y - c.getY()))
+        return c;
+    }
+
+    return null;
+  }
+
+  public static final Component getChildAtLine(Container container, Point p, boolean horizontal) {
+    if (horizontal) {
+      for (int i = 0; i < container.getComponentCount(); i++) {
+        Component c = container.getComponent(i);
+        if (p.x >= c.getX() && p.x < c.getX() + c.getWidth())
+          return c;
+      }
+    }
+    else {
+      for (int i = 0; i < container.getComponentCount(); i++) {
+        Component c = container.getComponent(i);
+        if (p.y >= c.getY() && p.y < c.getY() + c.getHeight())
+          return c;
+      }
+    }
+
+    return null;
+  }
+
+  public static void getComponentTreePosition(Component c, ArrayList pos) {
+    if (c.getParent() == null) {
+      return;
+    }
+
+    getComponentTreePosition(c.getParent(), pos);
+
+    pos.add(new Integer(c.getParent().getComponentCount() - ComponentUtil.getComponentIndex(c)));
+  }
+
+  public static Component findComponentUnderGlassPaneAt(Point p, Component top) {
+    Component c = null;
+
+    if (top.isShowing()) {
+      if (top instanceof RootPaneContainer)
+        c =
+        ((RootPaneContainer) top).getLayeredPane().findComponentAt(
+            SwingUtilities.convertPoint(top, p, ((RootPaneContainer) top).getLayeredPane()));
+      else
+        c = ((Container) top).findComponentAt(p);
+    }
+
+    return c;
+  }
+
+  public static final int getComponentIndex(Component component) {
+    if (component != null && component.getParent() != null) {
+      Container c = component.getParent();
+      for (int i = 0; i < c.getComponentCount(); i++) {
+        if (c.getComponent(i) == component)
+          return i;
+      }
+    }
+
+    return -1;
+  }
+
+  public static final String getBorderLayoutOrientation(Direction d) {
+    return d == Direction.UP ?
+           BorderLayout.NORTH :
+           d == Direction.LEFT ? BorderLayout.WEST : d == Direction.DOWN ? BorderLayout.SOUTH : BorderLayout.EAST;
+  }
+
+  public static Color getBackgroundColor(Component component) {
+    if (component == null)
+      return null;
+
+    if (component instanceof BackgroundPainter) {
+      ComponentPainter painter = ((BackgroundPainter) component).getComponentPainter();
+
+      if (painter != null) {
+        Color c = painter.getColor(component);
+
+        if (c != null)
+          return c;
+      }
+    }
+
+    return component.isOpaque() ? component.getBackground() : getBackgroundColor(component.getParent());
+  }
+
+  public static int countComponents(Container c) {
+    int num = 1;
+    for (int i = 0; i < c.getComponentCount(); i++) {
+      Component comp = c.getComponent(i);
+      if (comp instanceof Container)
+        num += countComponents((Container) comp);
+      else
+        num++;
+    }
+
+    return num;
+  }
+
+  public static int getVisibleChildrenCount(Component c) {
+    if (c == null || !(c instanceof Container))
+      return 0;
+
+    int count = 0;
+    Container container = (Container) c;
+
+    for (int i = 0; i < container.getComponentCount(); i++)
+      if (container.getComponent(i).isVisible())
+        count++;
+
+    return count;
+  }
+
+  public static Component getTopLevelAncestor(Component c) {
+    while (c != null) {
+      if (c instanceof Window || c instanceof Applet)
+        break;
+      c = c.getParent();
+    }
+    return c;
+  }
+
+  public static boolean hasVisibleChildren(Component c) {
+    return getVisibleChildrenCount(c) > 0;
+  }
+
+  public static boolean isOnlyVisibleComponent(Component c) {
+    return c != null && c.isVisible() && getVisibleChildrenCount(c.getParent()) == 1;
+  }
+
+  public static boolean isOnlyVisibleComponents(Component[] c) {
+    if (c != null && c.length > 0) {
+      boolean visible = getVisibleChildrenCount(c[0].getParent()) == c.length;
+      if (visible)
+        for (int i = 0; i < c.length; i++)
+          visible = visible && c[i].isVisible();
+      return visible;
+    }
+    return false;
+  }
+
+  public static Component findFirstComponentOfType(Component comp, Class c) {
+    if (c.isInstance(comp))
+      return comp;
+
+    if (comp instanceof Container) {
+      Container container = (Container) comp;
+      for (int i = 0; i < container.getComponentCount(); i++) {
+        Component comp2 = findFirstComponentOfType(container.getComponent(i), c);
+        if (comp2 != null)
+          return comp2;
+      }
+    }
+    return null;
+  }
+
+  public static boolean isFocusable(Component c) {
+    return c.isFocusable() && c.isDisplayable() && c.isVisible() && c.isEnabled();
+  }
+
+  /**
+   * Requests focus unless the component already has focus. For some weird
+   * reason calling {@link Component#requestFocusInWindow()}when the
+   * component is focus owner changes focus owner to another component!
+   *
+   * @param component the component to request focus for
+   * @return true if the component has focus or probably will get focus,
+   *         otherwise false
+   */
+  public static boolean requestFocus(Component component) {
+    /*
+     * System.out.println("Owner: " +
+     * System.identityHashCode(KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner()) + ", " +
+     * System.identityHashCode(component) + ", " +
+     * (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() ==
+     * component));
+     */
+    return KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() == component ||
+           component.requestFocusInWindow();
+  }
+
+  /**
+   * Requests focus for a component. If that's not possible it's
+   * {@link FocusTraversalPolicy}is checked. If that doesn't work all it's
+   * children is recursively checked with this method.
+   *
+   * @param component the component to request focus for
+   * @return the component which has focus or probably will obtain focus, null
+   *         if no component will receive focus
+   */
+  public static Component smartRequestFocus(Component component) {
+    if (requestFocus(component))
+      return component;
+
+    if (component instanceof JComponent) {
+      FocusTraversalPolicy policy = ((JComponent) component).getFocusTraversalPolicy();
+
+      if (policy != null) {
+        Component focusComponent = policy.getDefaultComponent((Container) component);
+
+        if (focusComponent != null && requestFocus(focusComponent)) {
+          return focusComponent;
+        }
+      }
+    }
+
+    if (component instanceof Container) {
+      Component[] children = ((Container) component).getComponents();
+
+      for (int i = 0; i < children.length; i++) {
+        component = smartRequestFocus(children[i]);
+
+        if (component != null)
+          return component;
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Calculates preferred max height for the given components without checking
+   * isVisible.
+   *
+   * @param components Components to check
+   * @return max height
+   */
+  public static int getPreferredMaxHeight(Component[] components) {
+    int height = 0;
+    for (int i = 0; i < components.length; i++) {
+      int k = (int) components[i].getPreferredSize().getHeight();
+      if (k > height)
+        height = k;
+    }
+    return height;
+  }
+
+  /**
+   * Calculates preferred max width for the given components without checking
+   * isVisible.
+   *
+   * @param components Components to check
+   * @return max width
+   */
+  public static int getPreferredMaxWidth(Component[] components) {
+    int width = 0;
+    for (int i = 0; i < components.length; i++) {
+      int k = (int) components[i].getPreferredSize().getWidth();
+      if (k > width)
+        width = k;
+    }
+    return width;
+  }
+
+  public static void setAllOpaque(Container c, boolean opaque) {
+    if (c instanceof JComponent) {
+      ((JComponent) c).setOpaque(opaque);
+      for (int i = 0; i < c.getComponentCount(); i++) {
+        Component comp = c.getComponent(i);
+        if (comp instanceof Container)
+          setAllOpaque((Container) comp, opaque);
+      }
+    }
+  }
+
+  public static void validate(JComponent c) {
+    c.revalidate();
+  }
+
+  public static void validate(Component c) {
+    if (c instanceof JComponent)
+      ((JComponent) c).revalidate();
+    else
+      c.validate();
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/ContainerList.java b/src/net/infonode/gui/ContainerList.java
new file mode 100644
index 0000000..688b6c8
--- /dev/null
+++ b/src/net/infonode/gui/ContainerList.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ContainerList.java,v 1.4 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui;
+
+import net.infonode.util.ChangeNotifyList;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class ContainerList extends ChangeNotifyList {
+  private Container container;
+
+  public ContainerList(Container container) {
+    super(new ArrayList());
+    this.container = container;
+  }
+
+  protected void changed() {
+    container.removeAll();
+    List list = getList();
+
+    for (int i = 0; i < list.size(); i++)
+      container.add((Component) list.get(i));
+  }
+}
diff --git a/src/net/infonode/gui/ContentTitleBar.java b/src/net/infonode/gui/ContentTitleBar.java
new file mode 100644
index 0000000..bc66d7c
--- /dev/null
+++ b/src/net/infonode/gui/ContentTitleBar.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ContentTitleBar.java,v 1.10 2009/02/05 15:57:56 jesper Exp $
+
+package net.infonode.gui;
+
+import java.awt.*;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+
+import net.infonode.gui.hover.panel.HoverableShapedPanel;
+import net.infonode.util.Alignment;
+import net.infonode.util.Direction;
+
+/**
+ * @author johan
+ */
+public class ContentTitleBar extends HoverableShapedPanel {
+  private final ComponentPaintChecker repaintChecker;
+  private JComponent[] leftTitleComponents;
+  private JComponent[] rightTitleComponents;
+  private Insets[] leftTitleComponentsInsets;
+  private Insets[] rightTitleComponentsInsets;
+
+  private boolean flipTitleComponents = false;
+
+  private GridBagConstraints constraints = new GridBagConstraints();
+
+  private Insets labelInsets = InsetsUtil.EMPTY_INSETS;
+  private Alignment labelAlignment = Alignment.LEFT;
+
+  private final RotatableLabel label = new RotatableLabel("") {
+    public Dimension getMinimumSize() {
+      Dimension d = super.getMinimumSize();
+      if (getDirection().isHorizontal())
+        return new Dimension(0, d.height);
+
+      return new Dimension(d.width, 0);
+    }
+  };
+
+  public ContentTitleBar() {
+    this(null);
+  }
+
+  public ContentTitleBar(Component hoveredComponent) {
+    super(new GridBagLayout(), null, hoveredComponent);
+    repaintChecker = new ComponentPaintChecker(this);
+    add(label);
+
+    updateLayout();
+  }
+
+  public JLabel getLabel() {
+    return label;
+  }
+
+  public String getText() {
+    return label.getText();
+  }
+
+  public Icon getIcon() {
+    return label.getIcon();
+  }
+
+  public void setIcon(Icon icon) {
+    if (label.getIcon() != icon) {
+      label.setIcon(icon);
+
+      doUpdate();
+    }
+  }
+
+  public Alignment getLabelAlignment() {
+    return labelAlignment;
+  }
+
+  public void setLabelAlignment(Alignment labelAlignment) {
+    if (this.labelAlignment != labelAlignment) {
+      this.labelAlignment = labelAlignment;
+      updateLabelAlignment();
+    }
+  }
+
+  public void setLayoutDirection(Direction direction) {
+    if (label.getDirection() != direction) {
+      label.setDirection(direction);
+      updateLayout();
+    }
+  }
+
+  public Insets getLabelInsets() {
+    return labelInsets;
+  }
+
+  public void setLabelInsets(Insets labelInsets) {
+    this.labelInsets = labelInsets;
+
+    GridBagConstraints c = ((GridBagLayout) getLayout()).getConstraints(label);
+    c.insets = InsetsUtil.rotate(getDirection(), labelInsets);
+    ((GridBagLayout) getLayout()).setConstraints(label, c);
+
+    doUpdate();
+  }
+
+  public boolean isFlipTitleComponents() {
+    return flipTitleComponents;
+  }
+
+  public void setFlipTitleComponents(boolean flipTitleComponents) {
+    if (this.flipTitleComponents != flipTitleComponents) {
+      this.flipTitleComponents = flipTitleComponents;
+      updateLayout();
+    }
+  }
+
+  public JComponent[] getLeftTitleComponents() {
+    return leftTitleComponents;
+  }
+
+  public void setLeftTitleComponents(JComponent[] leftTitleComponents) {
+    setLeftTitleComponents(leftTitleComponents,
+        leftTitleComponents == null ? null : createEmptyInsets(leftTitleComponents.length));
+  }
+
+  public void setLeftTitleComponents(JComponent[] leftTitleComponents, Insets[] leftTitleComponentsInsets) {
+    JComponent[] oldComponents = this.leftTitleComponents;
+    this.leftTitleComponents = leftTitleComponents;
+    this.leftTitleComponentsInsets = leftTitleComponentsInsets;
+    updateTitleComponents(oldComponents, leftTitleComponents);
+  }
+
+  public JComponent[] getRightTitleComponents() {
+    return rightTitleComponents;
+  }
+
+  public void setRightTitleComponents(JComponent[] rightTitleComponents) {
+    setRightTitleComponents(rightTitleComponents,
+        rightTitleComponents == null ? null : createEmptyInsets(rightTitleComponents.length));
+  }
+
+  public void setRightTitleComponents(JComponent[] rightTitleComponents, Insets[] rightTitleComponentsInsets) {
+    JComponent[] oldComponents = this.rightTitleComponents;
+    this.rightTitleComponents = rightTitleComponents;
+    this.rightTitleComponentsInsets = rightTitleComponentsInsets;
+    updateTitleComponents(oldComponents, rightTitleComponents);
+  }
+
+  private Insets[] createEmptyInsets(int num) {
+    Insets[] insets = new Insets[num];
+    for (int i = 0; i < num; i++) {
+      insets[i] = InsetsUtil.EMPTY_INSETS;
+    }
+
+    return insets;
+  }
+
+  private void updateLabelAlignment() {
+    label.setHorizontalAlignment(
+        labelAlignment == Alignment.LEFT ?
+                                          JLabel.LEFT : labelAlignment == Alignment.RIGHT ? JLabel.RIGHT : JLabel.CENTER);
+  }
+
+  private void updateTitleComponents(JComponent[] oldComponents, JComponent[] newComponents) {
+    if (oldComponents != null) {
+      for (int i = 0; i < oldComponents.length; i++)
+        remove(oldComponents[i]);
+    }
+
+    if (newComponents != null) {
+      for (int i = 0; i < newComponents.length; i++)
+        add(newComponents[i]);
+    }
+
+    updateLayout();
+  }
+
+  private void updateLayout() {
+    Direction direction = label.getDirection();
+    constraints = new GridBagConstraints();
+
+    JComponent[] leftComponents = flipTitleComponents ? rightTitleComponents : leftTitleComponents;
+    JComponent[] rightComponents = flipTitleComponents ? leftTitleComponents : rightTitleComponents;
+
+    Insets[] leftInsets = flipTitleComponents ? rightTitleComponentsInsets : leftTitleComponentsInsets;
+    Insets[] rightInsets = flipTitleComponents ? leftTitleComponentsInsets : rightTitleComponentsInsets;
+
+    if (direction == Direction.LEFT || direction == Direction.UP) {
+      JComponent[] tmpComponents = leftComponents;
+      leftComponents = rightComponents;
+      rightComponents = tmpComponents;
+
+      Insets[] tmpInsets = leftInsets;
+      leftInsets = rightInsets;
+      rightInsets = tmpInsets;
+    }
+
+    if (direction.isHorizontal()) {
+      int x = 0;
+
+      if (leftComponents != null) {
+        for (int i = 0; i < leftComponents.length; i++) {
+          int index = direction == Direction.RIGHT ? i : leftComponents.length - i - 1;
+          setConstraints(leftComponents[index],
+              leftInsets[index],
+              x++,
+              0,
+              1,
+              1,
+              GridBagConstraints.NONE,
+              0,
+              0,
+              GridBagConstraints.CENTER);
+        }
+      }
+
+      setConstraints(label, labelInsets, x++, 0, 1, 1, GridBagConstraints.BOTH, 1, 1, GridBagConstraints.CENTER);
+
+      if (rightComponents != null) {
+        for (int i = 0; i < rightComponents.length; i++) {
+          int index = direction == Direction.RIGHT ? i : rightComponents.length - i - 1;
+          setConstraints(rightComponents[index],
+              rightInsets[index],
+              x++,
+              0,
+              1,
+              1,
+              GridBagConstraints.NONE,
+              0,
+              0,
+              GridBagConstraints.CENTER);
+        }
+      }
+    }
+    else {
+      int y = 0;
+
+      if (leftComponents != null) {
+        for (int i = 0; i < leftComponents.length; i++) {
+          int index = direction == Direction.DOWN ? i : leftComponents.length - i - 1;
+          setConstraints(leftComponents[index],
+              leftInsets[index],
+              0,
+              y++,
+              1,
+              1,
+              GridBagConstraints.NONE,
+              0,
+              0,
+              GridBagConstraints.CENTER);
+        }
+      }
+
+      setConstraints(label, labelInsets, 0, y++, 1, 1, GridBagConstraints.BOTH, 1, 1, GridBagConstraints.CENTER);
+
+      if (rightComponents != null) {
+        for (int i = 0; i < rightComponents.length; i++) {
+          int index = direction == Direction.DOWN ? i : rightComponents.length - i - 1;
+          setConstraints(rightComponents[index],
+              rightInsets[index],
+              0,
+              y++,
+              1,
+              1,
+              GridBagConstraints.NONE,
+              0,
+              0,
+              GridBagConstraints.CENTER);
+        }
+      }
+    }
+
+    doUpdate();
+  }
+
+  private void setConstraints(Component c, Insets insets, int gridx, int gridy, int gridWidth, int gridHeight, int fill, double weightx, double weighty, int anchor) {
+    constraints.insets = InsetsUtil.rotate(getDirection(), insets);
+    constraints.gridx = gridx;
+    constraints.gridy = gridy;
+    constraints.fill = fill;
+    constraints.weightx = weightx;
+    constraints.weighty = weighty;
+    constraints.gridwidth = gridWidth;
+    constraints.gridheight = gridHeight;
+    constraints.anchor = anchor;
+
+    ((GridBagLayout) getLayout()).setConstraints(c, constraints);
+  }
+
+  private void doUpdate() {
+    revalidate();
+
+    if (repaintChecker.isPaintingOk())
+      repaint();
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/CursorManager.java b/src/net/infonode/gui/CursorManager.java
new file mode 100644
index 0000000..7673f5a
--- /dev/null
+++ b/src/net/infonode/gui/CursorManager.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: CursorManager.java,v 1.17 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.util.WeakHashMap;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.17 $
+ */
+public class CursorManager {
+  private static class RootCursorInfo {
+    private Cursor savedCursor;
+    private Cursor cursor;
+    private JComponent panel;
+
+    private boolean cursorSet = false;
+
+    RootCursorInfo(JComponent panel) {
+      this.panel = panel;
+    }
+
+    public JComponent getComponent() {
+      return panel;
+    }
+
+    public void pushCursor(Cursor cursor) {
+      if (savedCursor == null)
+        savedCursor = cursor;
+
+      cursorSet = true;
+    }
+
+    public Cursor popCursor() {
+      Cursor c = savedCursor;
+      savedCursor = null;
+
+      cursorSet = false;
+      return c;
+    }
+
+    public boolean isCursorSet() {
+      return cursorSet;
+    }
+
+    public Cursor getCursor() {
+      return cursor;
+    }
+
+    public void setCursor(Cursor cursor) {
+      this.cursor = cursor;
+    }
+
+  }
+
+  private static boolean enabled = true;
+  private static WeakHashMap windowPanels = new WeakHashMap();
+
+  private CursorManager() {
+  }
+
+  public static void setGlobalCursor(final JRootPane root, Cursor cursor) {
+    if (root == null)
+      return;
+
+    RootCursorInfo rci = (RootCursorInfo) windowPanels.get(root);
+
+    if (rci == null) {
+      rci = new RootCursorInfo(new JComponent() {
+      });
+      windowPanels.put(root, rci);
+      root.getLayeredPane().add(rci.getComponent());
+      root.getLayeredPane().setLayer(rci.getComponent(), JLayeredPane.DRAG_LAYER.intValue() + 10);
+      rci.getComponent().setBounds(0, 0, root.getWidth(), root.getHeight());
+      root.getLayeredPane().addComponentListener(new ComponentAdapter() {
+        public void componentResized(ComponentEvent e) {
+          ((RootCursorInfo) windowPanels.get(root)).getComponent().setSize(root.getSize());
+        }
+      });
+    }
+
+    if (!rci.isCursorSet()) {
+      rci.setCursor(cursor);
+      rci.pushCursor(root.isCursorSet() ? root.getCursor() : null);
+    }
+
+    if (enabled) {
+      root.setCursor(cursor);
+      rci.getComponent().setVisible(true);
+    }
+  }
+
+  public static Cursor getCurrentGlobalCursor(JRootPane root) {
+    if (root == null)
+      return Cursor.getDefaultCursor();
+
+    RootCursorInfo rci = (RootCursorInfo) windowPanels.get(root);
+    return rci == null || !rci.isCursorSet() ? Cursor.getDefaultCursor() : rci.getCursor();
+  }
+
+  public static void resetGlobalCursor(JRootPane root) {
+    if (root == null)
+      return;
+
+    RootCursorInfo rci = (RootCursorInfo) windowPanels.get(root);
+
+    if (rci != null && rci.isCursorSet()) {
+      root.setCursor(rci.popCursor());
+      rci.getComponent().setVisible(false);
+    }
+  }
+
+  public static void setEnabled(boolean enabled) {
+    CursorManager.enabled = enabled;
+  }
+
+  public static boolean isEnabled() {
+    return enabled;
+  }
+
+  public static JComponent getCursorLayerComponent(JRootPane root) {
+    if (root == null)
+      return null;
+
+    RootCursorInfo rci = (RootCursorInfo) windowPanels.get(root);
+    return rci == null ? null : rci.getComponent();
+  }
+
+  public static boolean isGlobalCursorSet(JRootPane root) {
+    if (root == null)
+      return false;
+
+    RootCursorInfo rci = (RootCursorInfo) windowPanels.get(root);
+    return rci != null && rci.isCursorSet();
+  }
+}
diff --git a/src/net/infonode/gui/DimensionProvider.java b/src/net/infonode/gui/DimensionProvider.java
new file mode 100644
index 0000000..108e2a7
--- /dev/null
+++ b/src/net/infonode/gui/DimensionProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+package net.infonode.gui;
+
+import java.awt.*;
+
+/**
+ * An object that provides dimensions.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ */
+public interface DimensionProvider {
+
+  /**
+   * Returns the dimension for the given component
+   *
+   * @param c the component to get dimension for
+   * @return the dimension
+   */
+  public Dimension getDimension(Component c);
+}
diff --git a/src/net/infonode/gui/DimensionUtil.java b/src/net/infonode/gui/DimensionUtil.java
new file mode 100644
index 0000000..e6dc0e6
--- /dev/null
+++ b/src/net/infonode/gui/DimensionUtil.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DimensionUtil.java,v 1.11 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.11 $
+ */
+public class DimensionUtil {
+  public static final Dimension ZERO = new Dimension(0, 0);
+
+  public static Dimension min(Dimension d1, Dimension d2) {
+    return new Dimension(Math.min((int) d1.getWidth(), (int) d2.getWidth()),
+                         Math.min((int) d1.getHeight(), (int) d2.getHeight()));
+  }
+
+  public static Dimension max(Dimension d1, Dimension d2) {
+    return new Dimension(Math.max((int) d1.getWidth(), (int) d2.getWidth()),
+                         Math.max((int) d1.getHeight(), (int) d2.getHeight()));
+  }
+
+  public static Dimension getInnerDimension(Dimension d, Insets i) {
+    return new Dimension((int) (d.getWidth() - i.left - i.right),
+                         (int) (d.getHeight() - i.top - i.bottom));
+  }
+
+  public static Dimension add(Dimension dim, Insets insets) {
+    return new Dimension(dim.width + insets.left + insets.right, dim.height + insets.top + insets.bottom);
+  }
+
+  public static Dimension add(Dimension d1, Dimension d2, boolean isHorizontalAdd) {
+    return new Dimension(isHorizontalAdd ? (d1.width + d2.width) : Math.max(d1.width, d2.width),
+                         isHorizontalAdd ? Math.max(d1.height, d2.height) : d1.height + d2.height);
+  }
+
+  public static Dimension rotate(Direction source, Dimension d, Direction destination) {
+    if (source.isHorizontal() && destination.isHorizontal())
+      return d;
+
+    return new Dimension(d.height, d.width);
+  }
+}
diff --git a/src/net/infonode/gui/DragLabelWindow.java b/src/net/infonode/gui/DragLabelWindow.java
new file mode 100644
index 0000000..163ca72
--- /dev/null
+++ b/src/net/infonode/gui/DragLabelWindow.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DragLabelWindow.java,v 1.8 2005/12/04 13:46:04 jesper Exp $
+
+package net.infonode.gui;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author johan
+ */
+public class DragLabelWindow extends Dialog {
+  private JLabel label = new JLabel() {
+    public Dimension getMinimumSize() {
+      return getPreferredSize();
+    }
+  };
+
+  public DragLabelWindow(Dialog d) {
+    super(d);
+    init();
+  }
+
+  public DragLabelWindow(Frame f) {
+    super(f);
+    init();
+  }
+
+  private void init() {
+    setLayout(new BorderLayout());
+    add(label, BorderLayout.NORTH);
+    setUndecorated(true);
+    label.setOpaque(true);
+  }
+
+  public JLabel getLabel() {
+    return label;
+  }
+
+  public void setCursor(Cursor c) {
+    label.setCursor(c);
+  }
+
+  public void setVisible(boolean visible) {
+    if (visible != isVisible()) {
+      if (visible) {
+        setSize(0, 0);
+        pack();
+        setSize(getPreferredSize());
+      }
+
+      //label.setCursor(visible ? DragSource.DefaultMoveDrop : Cursor.getDefaultCursor());
+    }
+
+    super.setVisible(visible);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/DynamicUIManager.java b/src/net/infonode/gui/DynamicUIManager.java
new file mode 100644
index 0000000..6d19d2d
--- /dev/null
+++ b/src/net/infonode/gui/DynamicUIManager.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DynamicUIManager.java,v 1.11 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+import javax.swing.*;
+import java.awt.*;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.ArrayList;
+
+public class DynamicUIManager {
+  private static final DynamicUIManager instance = new DynamicUIManager();
+
+  private ArrayList listeners = new ArrayList(2);
+  private ArrayList prioritizedListeners = new ArrayList(2);
+
+  private String[] properties = {"win.3d.backgroundColor",
+                                 "win.3d.highlightColor",
+                                 "win.3d.lightColor",
+                                 "win.3d.shadowColor",
+                                 "win.frame.activeCaptionColor",
+                                 "win.frame.activeCaptionGradientColor",
+                                 "win.frame.captionTextColor",
+                                 "win.frame.activeBorderColor",
+                                 "win.mdi.backgroundColor",
+                                 "win.desktop.backgroundColor",
+                                 "win.frame.inactiveCaptionColor",
+                                 "win.frame.inactiveCaptionGradientColor",
+                                 "win.frame.inactiveCaptionTextColor",
+                                 "win.frame.inactiveBorderColor",
+                                 "win.menu.backgroundColor",
+                                 "win.menu.textColor",
+                                 "win.frame.textColor?????",
+                                 "win.item.highlightColor",
+                                 "win.item.highlightTextColor",
+                                 "win.tooltip.backgroundColor",
+                                 "win.tooltip.textColor",
+                                 "win.frame.backgroundColor",
+                                 "win.frame.textColor",
+                                 "win.item.hotTrackedColor"};
+  private Toolkit currentToolkit;
+  private boolean propertyChangePending;
+
+  public static DynamicUIManager getInstance() {
+    return instance;
+  }
+
+  private DynamicUIManager() {
+    final PropertyChangeListener l = new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent event) {
+        handlePropertyChanges();
+      }
+    };
+
+    UIManager.addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent event) {
+        if (event.getPropertyName().equals("lookAndFeel")) {
+          setupPropertyListener(l);
+          fireLookAndFeelChanging();
+          fireLookAndFeelChanged();
+        }
+      }
+    });
+    UIManager.getDefaults().addPropertyChangeListener(new PropertyChangeListener() {
+      public void propertyChange(PropertyChangeEvent event) {
+        if (!(event.getNewValue() instanceof Class))
+          handlePropertyChanges();
+      }
+    });
+
+    setupPropertyListener(l);
+  }
+
+  private void setupPropertyListener(PropertyChangeListener l) {
+    if (currentToolkit != null)
+      for (int i = 0; i < properties.length; i++)
+        currentToolkit.removePropertyChangeListener(properties[i], l);
+
+    currentToolkit = Toolkit.getDefaultToolkit();
+    for (int i = 0; i < properties.length; i++) {
+      currentToolkit.addPropertyChangeListener(properties[i], l);
+    }
+  }
+
+  public void addListener(DynamicUIManagerListener l) {
+    listeners.add(l);
+  }
+
+  public void removeListener(DynamicUIManagerListener l) {
+    listeners.remove(l);
+  }
+
+  public void addPrioritizedListener(DynamicUIManagerListener l) {
+    prioritizedListeners.add(l);
+  }
+
+  public void removePrioritizedListener(DynamicUIManagerListener l) {
+    prioritizedListeners.remove(l);
+  }
+
+  private void fireLookAndFeelChanging() {
+    Object l[] = prioritizedListeners.toArray();
+    Object l2[] = listeners.toArray();
+
+    for (int i = 0; i < l.length; i++)
+      ((DynamicUIManagerListener) l[i]).lookAndFeelChanging();
+
+    for (int i = 0; i < l2.length; i++)
+      ((DynamicUIManagerListener) l2[i]).lookAndFeelChanging();
+  }
+
+  private void fireLookAndFeelChanged() {
+    Object l[] = prioritizedListeners.toArray();
+    Object l2[] = listeners.toArray();
+
+    for (int i = 0; i < l.length; i++)
+      ((DynamicUIManagerListener) l[i]).lookAndFeelChanged();
+
+    for (int i = 0; i < l2.length; i++)
+      ((DynamicUIManagerListener) l2[i]).lookAndFeelChanged();
+  }
+
+  private void handlePropertyChanges() {
+    if (!propertyChangePending) {
+      propertyChangePending = true;
+
+      SwingUtilities.invokeLater(new Runnable() {
+        public void run() {
+          propertyChangePending = false;
+
+          firePropertyChanged();
+        }
+      });
+
+      firePropertyChanging();
+    }
+  }
+
+  private void firePropertyChanging() {
+    Object l[] = prioritizedListeners.toArray();
+    Object l2[] = listeners.toArray();
+
+    for (int i = 0; i < l.length; i++)
+      ((DynamicUIManagerListener) l[i]).propertiesChanging();
+
+    for (int i = 0; i < l2.length; i++)
+      ((DynamicUIManagerListener) l2[i]).propertiesChanging();
+  }
+
+  private void firePropertyChanged() {
+    Object l[] = prioritizedListeners.toArray();
+    Object l2[] = listeners.toArray();
+
+    for (int i = 0; i < l.length; i++)
+      ((DynamicUIManagerListener) l[i]).propertiesChanged();
+
+    for (int i = 0; i < l2.length; i++)
+      ((DynamicUIManagerListener) l2[i]).propertiesChanged();
+  }
+}
+
diff --git a/src/net/infonode/gui/DynamicUIManagerListener.java b/src/net/infonode/gui/DynamicUIManagerListener.java
new file mode 100644
index 0000000..7a826cb
--- /dev/null
+++ b/src/net/infonode/gui/DynamicUIManagerListener.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DynamicUIManagerListener.java,v 1.7 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+public interface DynamicUIManagerListener {
+  public void lookAndFeelChanging();
+
+  public void lookAndFeelChanged();
+
+  public void propertiesChanging();
+
+  public void propertiesChanged();
+}
diff --git a/src/net/infonode/gui/EventUtil.java b/src/net/infonode/gui/EventUtil.java
new file mode 100644
index 0000000..e668fc9
--- /dev/null
+++ b/src/net/infonode/gui/EventUtil.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: EventUtil.java,v 1.4 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class EventUtil {
+  private EventUtil() {
+  }
+
+  public static MouseEvent convert(MouseEvent event, Component newSource) {
+    return convert(event,
+                   newSource,
+                   SwingUtilities.convertPoint((Component) event.getSource(), event.getPoint(), newSource));
+  }
+
+  public static MouseEvent convert(MouseEvent event, Component newSource, Point p) {
+    return new MouseEvent(newSource,
+                          event.getID(),
+                          event.getWhen(),
+                          event.getModifiers(),
+                          p.x,
+                          p.y,
+                          event.getClickCount(),
+                          event.isPopupTrigger(),
+                          event.getButton());
+  }
+}
diff --git a/src/net/infonode/gui/FlatIconButtonUI.java b/src/net/infonode/gui/FlatIconButtonUI.java
new file mode 100644
index 0000000..e7acf4f
--- /dev/null
+++ b/src/net/infonode/gui/FlatIconButtonUI.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FlatIconButtonUI.java,v 1.4 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicButtonUI;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class FlatIconButtonUI extends BasicButtonUI {
+  private final static FlatIconButtonUI buttonUI = new FlatIconButtonUI();
+
+  public static ComponentUI createUI(JComponent c) {
+    return buttonUI;
+  }
+
+  protected void installDefaults(AbstractButton b) {
+  }
+
+/*  public void paint(Graphics g, JComponent c) {
+    AbstractButton button = (AbstractButton) c;
+    Insets i = getInsets(button);
+    button.getIcon().paintIcon(c, g, i.left, i.top);
+    paintIcon(g, c, new Rectangle(i.left, i.top, c.getWidth(), c.getHeight()));
+  }
+
+  public Dimension getPreferredSize(JComponent c) {
+    AbstractButton button = (AbstractButton) c;
+    Insets i = getInsets(button);
+    return new Dimension(i.left + i.right + button.getIcon().getIconWidth(),
+                         i.top + i.bottom + button.getIcon().getIconWidth());
+  }
+
+  private Insets getInsets(AbstractButton button) {
+    return InsetsUtil.add(button.getInsets(), button.getMargin());
+  }
+*/
+}
diff --git a/src/net/infonode/gui/FontUtil.java b/src/net/infonode/gui/FontUtil.java
new file mode 100644
index 0000000..7959dd0
--- /dev/null
+++ b/src/net/infonode/gui/FontUtil.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FontUtil.java,v 1.5 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui;
+
+import java.awt.*;
+
+public class FontUtil {
+  private FontUtil() {
+  }
+
+  public static Font copy(Font font) {
+    return font == null ? null : new Font(font.getName(), font.getStyle(), font.getSize());
+  }
+}
diff --git a/src/net/infonode/gui/GraphicsUtil.java b/src/net/infonode/gui/GraphicsUtil.java
new file mode 100644
index 0000000..5bfc17f
--- /dev/null
+++ b/src/net/infonode/gui/GraphicsUtil.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: GraphicsUtil.java,v 1.4 2005/12/04 13:46:04 jesper Exp $
+
+package net.infonode.gui;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author johan
+ */
+public class GraphicsUtil {
+  public static void drawOptimizedLine(Graphics g, int x1, int y1, int x2, int y2) {
+    if (g.getColor().getAlpha() < 255 && (x1 == x2 || y1 == y2))
+      g.fillRect(x1 < x2 ? x1 : x2, y1 < y2 ? y1 : y2, Math.abs(x2 - x1) + 1, Math.abs(y2 - y1) + 1);
+    else
+      g.drawLine(x1, y1, x2, y2);
+  }
+
+  public static Rectangle calculateIntersectionClip(int x, int y, int width, int height, Shape originalClip) {
+    Rectangle bounds = originalClip.getBounds();
+    SwingUtilities.computeIntersection(x, y, width, height, bounds);
+    return bounds;
+  }
+}
diff --git a/src/net/infonode/gui/HighlightPainter.java b/src/net/infonode/gui/HighlightPainter.java
new file mode 100644
index 0000000..0f7bb8f
--- /dev/null
+++ b/src/net/infonode/gui/HighlightPainter.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: HighlightPainter.java,v 1.6 2005/04/14 11:59:19 johan Exp $
+package net.infonode.gui;
+
+import net.infonode.util.ColorUtil;
+
+import java.awt.*;
+
+/**
+ * @author $Author: johan $
+ * @version $Revision: 1.6 $
+ */
+public class HighlightPainter {
+  private HighlightPainter() {
+  }
+
+  public static void drawLine(Graphics g, int x1, int y1, int x2, int y2, boolean clockWise, boolean inside,
+                              Color highlightColor, Color middleColor, Color shadowColor) {
+    int mul = clockWise ? 1 : -1;
+    int dx = (x2 - x1) * mul;
+    int dy = (y2 - y1) * mul;
+    int l2 = 2 * (dx * dx + dy * dy);
+    int a = dx - dy;
+    Color blendColor = a > 0 ? highlightColor : shadowColor;
+
+    if (blendColor != null) {
+      g.setColor(ColorUtil.blend(middleColor, blendColor, (float) a * a / l2));
+      int hx = inside ? getHighlightOffsetX(dx, dy) : 0;
+      int hy = inside ? getHighlightOffsetY(dx, dy) : 0;
+      GraphicsUtil.drawOptimizedLine(g, x1 + hx, y1 + hy, x2 + hx, y2 + hy);
+    }
+
+  }
+
+  public static float getBlendFactor(int dx, int dy) {
+    int l2 = 2 * (dx * dx + dy * dy);
+    int a = dx - dy;
+    return 1 - (float) a * a / l2;
+  }
+
+  protected static int getHighlightOffsetX(int deltaX, int deltaY) {
+    return deltaY - deltaX > 0 ? (deltaX + deltaY > 0 ? -1 : 0) : (deltaX + deltaY > 0 ? 0 : 1); //-deltaY > deltaX ? 1 : 0;
+  }
+
+  protected static int getHighlightOffsetY(int deltaX, int deltaY) {
+    return deltaY - deltaX > 0 ? (deltaX + deltaY > 0 ? 0 : -1) : (deltaX + deltaY > 0 ? 1 : 0); //-deltaY > deltaX ? 1 : 0;
+  }
+
+
+}
diff --git a/src/net/infonode/gui/InsetsUtil.java b/src/net/infonode/gui/InsetsUtil.java
new file mode 100644
index 0000000..b216e62
--- /dev/null
+++ b/src/net/infonode/gui/InsetsUtil.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InsetsUtil.java,v 1.14 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+public class InsetsUtil {
+  public static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
+
+  private InsetsUtil() {
+  }
+
+  public static Insets getDiff(Insets source, Insets other) {
+    int top = other.top - source.top;
+    int left = other.left - source.left;
+    int bottom = other.bottom - source.bottom;
+    int right = other.right - source.right;
+    return new Insets(top > 0 ? top : 0,
+                      left > 0 ? left : 0,
+                      bottom > 0 ? bottom : 0,
+                      right > 0 ? right : 0);
+  }
+
+  public static Insets sub(Insets i1, Insets i2) {
+    return new Insets(i1.top - i2.top,
+                      i1.left - i2.left,
+                      i1.bottom - i2.bottom,
+                      i1.right - i2.right);
+  }
+
+  public static Insets add(Insets i, Insets i2) {
+    return new Insets(i.top + i2.top,
+                      i.left + i2.left,
+                      i.bottom + i2.bottom,
+                      i.right + i2.right);
+  }
+
+  public static final Insets flip(Insets insets, boolean horizontalFlip, boolean verticalFlip) {
+    return horizontalFlip ? (verticalFlip ? new Insets(insets.bottom, insets.right, insets.top, insets.left) :
+                             new Insets(insets.top, insets.right, insets.bottom, insets.left)) :
+           (verticalFlip ? new Insets(insets.bottom, insets.left, insets.top, insets.right) :
+            insets);
+  }
+
+  public static final Insets rotate(Direction d, Insets insets) {
+    if (d == Direction.LEFT)
+      return new Insets(insets.bottom,
+                        insets.right,
+                        insets.top,
+                        insets.left);
+    else if (d == Direction.DOWN)
+      return new Insets(insets.left,
+                        insets.bottom,
+                        insets.right,
+                        insets.top);
+    else if (d == Direction.UP)
+      return new Insets(insets.right,
+                        insets.top,
+                        insets.left,
+                        insets.bottom);
+    return (Insets) insets.clone();
+  }
+
+  public static Insets max(Insets insets1, Insets insets2) {
+    return new Insets(Math.max(insets1.top, insets2.top),
+                      Math.max(insets1.left, insets2.left),
+                      Math.max(insets1.bottom, insets2.bottom),
+                      Math.max(insets1.right, insets2.right));
+  }
+
+  public static int maxInset(Insets i) {
+    return Math.max(i.top, Math.max(i.bottom, Math.max(i.left, i.right)));
+  }
+
+  public static int getInset(Insets insets, Direction direction) {
+    return direction == Direction.UP ? insets.top :
+           direction == Direction.LEFT ? insets.left :
+           direction == Direction.DOWN ? insets.bottom :
+           insets.right;
+  }
+
+  public static Insets setInset(Insets insets, Direction direction, int value) {
+    return direction == Direction.UP ? new Insets(value, insets.left, insets.bottom, insets.right) :
+           direction == Direction.LEFT ? new Insets(insets.top, value, insets.bottom, insets.right) :
+           direction == Direction.DOWN ? new Insets(insets.top, insets.left, value, insets.right) :
+           new Insets(insets.top, insets.left, insets.bottom, value);
+  }
+
+  public static Insets copy(Insets insets) {
+    return insets == null ? null : new Insets(insets.top, insets.left, insets.bottom, insets.right);
+  }
+
+  public static void addTo(Insets insets, Insets addition) {
+    insets.top += addition.top;
+    insets.bottom += addition.bottom;
+    insets.left += addition.left;
+    insets.right += addition.right;
+  }
+
+  public static Insets flipHorizontal(Insets insets) {
+    return new Insets(insets.top, insets.right, insets.bottom, insets.left);
+  }
+
+  public static Insets flipVertical(Insets insets) {
+    return new Insets(insets.bottom, insets.left, insets.top, insets.right);
+  }
+}
+
diff --git a/src/net/infonode/gui/PopupList.java b/src/net/infonode/gui/PopupList.java
new file mode 100644
index 0000000..fbda735
--- /dev/null
+++ b/src/net/infonode/gui/PopupList.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PopupList.java,v 1.13 2005/06/29 11:57:40 johan Exp $
+package net.infonode.gui;
+
+import net.infonode.gui.panel.SimplePanel;
+
+import javax.swing.*;
+import javax.swing.border.LineBorder;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.ArrayList;
+
+public class PopupList extends SimplePanel {
+  private class PopupButtonModel extends DefaultButtonModel {
+    private boolean pressed;
+
+    public boolean isPressed() {
+      return super.isPressed() || pressed;
+    }
+
+    public boolean isArmed() {
+      return super.isArmed() || pressed;
+    }
+
+    public void setPressedInternal(boolean pressed) {
+      this.pressed = pressed;
+      fireStateChanged();
+    }
+  }
+
+  private class Popup extends JPopupMenu {
+    private JList list = new JList();
+    private JScrollPane scrollPane = new JScrollPane(list,
+                                                     JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+                                                     JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
+    private int oldIndex;
+
+    Popup() {
+      setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+      scrollPane.setBorder(null);
+      setBorderPainted(true);
+      setBorder(new LineBorder(UIManagerUtil.getColor("controlDkShadow", Color.BLACK), 1));
+
+      add(scrollPane);
+      scrollPane.getViewport().setOpaque(false);
+      list.addListSelectionListener(new ListSelectionListener() {
+        public void valueChanged(ListSelectionEvent e) {
+          if (!e.getValueIsAdjusting())
+            setVisible(false);
+        }
+      });
+
+      update();
+    }
+
+    public MouseMotionListener getMouseMotionListener() {
+      return new MouseMotionAdapter() {
+        public void mouseDragged(MouseEvent e) {
+          if (SwingUtilities.isLeftMouseButton(e)) {
+            Component c = (Component) e.getSource();
+            Point p = SwingUtilities.convertPoint(c, e.getPoint(), scrollPane);
+            int index = list.locationToIndex(SwingUtilities.convertPoint(scrollPane, p, list));
+            if (!c.contains(e.getPoint()) &&
+                (scrollPane.contains(p) || (p.getY() > scrollPane.getY() + scrollPane.getHeight()) ||
+                 p.getY() < scrollPane.getY())) {
+              list.setSelectedIndex(index);
+              list.ensureIndexIsVisible(index);
+            }
+          }
+        }
+      };
+    }
+
+    public MouseListener getMouseListener() {
+      return new MouseAdapter() {
+        public void mousePressed(MouseEvent e) {
+          if (SwingUtilities.isLeftMouseButton(e)) {
+            if (isVisible()) {
+              setVisible(false);
+              return;
+            }
+            update();
+            scrollPane.setViewportView(null);
+            list.setValueIsAdjusting(true);
+            fireWillBecomeVisible();
+            list.setVisibleRowCount(Math.min(list.getModel().getSize(), 8));
+            oldIndex = list.getSelectedIndex();
+            list.ensureIndexIsVisible(oldIndex);
+            scrollPane.setViewportView(list);
+            Component c = (Component) e.getSource();
+            show(c, 0, c.getHeight());
+          }
+        }
+
+        public void mouseReleased(MouseEvent e) {
+          if (SwingUtilities.isLeftMouseButton(e)) {
+            if (!isVisible())
+              return;
+
+            Point p = SwingUtilities.convertPoint((Component) e.getSource(), e.getPoint(), scrollPane);
+            if (scrollPane.contains(p)) {
+              list.setValueIsAdjusting(false);
+            }
+            else if (!((Component) e.getSource()).contains(e.getPoint())) {
+              list.setSelectedIndex(oldIndex);
+              list.setValueIsAdjusting(false);
+            }
+          }
+        }
+      };
+    }
+
+    public JList getList() {
+      return list;
+    }
+
+    public void updateUI() {
+      super.updateUI();
+      setBorder(new LineBorder(UIManagerUtil.getColor("controlDkShadow", Color.BLACK), 1));
+      if (list != null)
+        update();
+    }
+
+    private void update() {
+      /*list.setFont(UIManager.getFont("ComboBox.font"));
+      list.setForeground(UIManagerUtil.getColor("ComboBox.foreground"));
+      list.setBackground(UIManagerUtil.getColor("ComboBox.background", "control"));
+      list.setSelectionForeground(UIManagerUtil.getColor("ComboBox.selectionForeground"));
+      list.setSelectionBackground(UIManagerUtil.getColor("ComboBox.selectionBackground"));
+      list.setBorder(null);*/
+      scrollPane.getViewport().setOpaque(false);
+      scrollPane.setBorder(null);
+    }
+  }
+
+  private Popup popup = new Popup();
+  private ArrayList listeners = new ArrayList(1);
+
+  public PopupList(AbstractButton component) {
+    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
+    setButton(component);
+
+    popup.addPopupMenuListener(new PopupMenuListener() {
+      public void popupMenuCanceled(PopupMenuEvent e) {
+      }
+
+      public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+        ((PopupButtonModel) ((AbstractButton) getComponent(0)).getModel()).setPressedInternal(false);
+      }
+
+      public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+        ((PopupButtonModel) ((AbstractButton) getComponent(0)).getModel()).setPressedInternal(true);
+      }
+    });
+  }
+
+  public JList getList() {
+    return popup.getList();
+  }
+
+  public void setButton(AbstractButton component) {
+    if (getComponentCount() > 0) {
+      AbstractButton c = (AbstractButton) getComponent(0);
+      c.removeMouseListener(popup.getMouseListener());
+      c.removeMouseMotionListener(popup.getMouseMotionListener());
+      remove(c);
+    }
+
+    add(component);
+    component.setModel(new PopupButtonModel());
+    component.setAutoscrolls(true);
+    component.setFocusable(false);
+    component.addMouseListener(popup.getMouseListener());
+    component.addMouseMotionListener(popup.getMouseMotionListener());
+  }
+
+  public AbstractButton getButton() {
+    return getComponentCount() == 0 ? null : (AbstractButton) getComponent(0);
+  }
+
+  public void updateUI() {
+    super.updateUI();
+    if (popup != null)
+      SwingUtilities.updateComponentTreeUI(popup);
+  }
+
+  public void addPopupListListener(PopupListListener l) {
+    listeners.add(l);
+  }
+
+  public void removePopupListListener(PopupListListener l) {
+    listeners.remove(l);
+  }
+
+  public void addListSelectionListener(ListSelectionListener l) {
+    getList().addListSelectionListener(l);
+  }
+
+  public void removeListSelectionListener(ListSelectionListener l) {
+    getList().removeListSelectionListener(l);
+  }
+
+  private void fireWillBecomeVisible() {
+    Object[] l = listeners.toArray();
+    for (int i = 0; i < l.length; i++)
+      ((PopupListListener) l[i]).willBecomeVisible(this);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/PopupListListener.java b/src/net/infonode/gui/PopupListListener.java
new file mode 100644
index 0000000..014eeee
--- /dev/null
+++ b/src/net/infonode/gui/PopupListListener.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PopupListListener.java,v 1.4 2004/09/22 14:35:05 jesper Exp $
+package net.infonode.gui;
+
+/**
+ * @author johan
+ */
+public interface PopupListListener {
+  void willBecomeVisible(PopupList list);
+}
diff --git a/src/net/infonode/gui/RectangleUtil.java b/src/net/infonode/gui/RectangleUtil.java
new file mode 100644
index 0000000..c055fcf
--- /dev/null
+++ b/src/net/infonode/gui/RectangleUtil.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RectangleUtil.java,v 1.5 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class RectangleUtil {
+  private RectangleUtil() {
+  }
+
+  public static Rectangle transform(Rectangle rectangle,
+                                    Direction direction,
+                                    boolean horizontalFlip,
+                                    boolean verticalFlip,
+                                    int width,
+                                    int height) {
+    int x = horizontalFlip ? (direction.isHorizontal() ? width : height) - rectangle.width - rectangle.x : rectangle.x;
+    int y = verticalFlip ? (direction.isHorizontal() ? height : width) - rectangle.height - rectangle.y : rectangle.y;
+
+    return direction == Direction.DOWN ? new Rectangle(width - y - rectangle.height,
+                                                       x,
+                                                       rectangle.height,
+                                                       rectangle.width) :
+           direction == Direction.LEFT ? new Rectangle(width - x - rectangle.width,
+                                                       height - y - rectangle.height,
+                                                       rectangle.width,
+                                                       rectangle.height) :
+           direction == Direction.UP ? new Rectangle(y,
+                                                     height - x - rectangle.width,
+                                                     rectangle.height,
+                                                     rectangle.width) :
+           new Rectangle(x, y, rectangle.width, rectangle.height);
+  }
+}
diff --git a/src/net/infonode/gui/ReleaseInfoDialog.java b/src/net/infonode/gui/ReleaseInfoDialog.java
new file mode 100644
index 0000000..bd9f8d6
--- /dev/null
+++ b/src/net/infonode/gui/ReleaseInfoDialog.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ReleaseInfoDialog.java,v 1.16 2005/12/04 13:46:04 jesper Exp $
+
+package net.infonode.gui;
+
+import net.infonode.util.ReleaseInfo;
+
+import javax.swing.*;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.TitledBorder;
+import java.awt.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+public class ReleaseInfoDialog {
+  private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss z");
+
+  static {
+    DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("UTC"));
+  }
+
+  public static void showDialog(ReleaseInfo info, String text) {
+    showDialog(new ReleaseInfo[]{info}, text == null ? null : new String[]{text});
+  }
+
+  public static void showDialog(ReleaseInfo[] info, String[] text) {
+    final JComponent message = constructMessage(info, text);
+    JScrollPane scrollPane = new JScrollPane(message,
+                                             JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
+                                             JScrollPane.HORIZONTAL_SCROLLBAR_NEVER) {
+      public Dimension getPreferredSize() {
+        Dimension d = message.getPreferredSize();
+        int height = (int) d.getHeight();
+        return new Dimension((int) d.getWidth() + 50, height < 300 ? (int) super.getPreferredSize().getHeight() : 400);
+      }
+    };
+    message.setBorder(new EmptyBorder(10, 20, 10, 20));
+    scrollPane.getViewport().setBackground(Color.white);
+    JOptionPane.showMessageDialog(null, scrollPane, "Product Release Information", JOptionPane.INFORMATION_MESSAGE);
+  }
+
+  private static JComponent constructMessage(ReleaseInfo[] info, String[] text) {
+    Box box = new Box(BoxLayout.Y_AXIS);
+    for (int i = 0; i < info.length; i++) {
+      JLabel l = new JLabel("<html><body>" + (text == null || text[i] == null ? "" : text[i] + "<br>") + "<table>" +
+                            "<tr><td style='font-weight: bold;'>Product Name:</td><td>"
+                            + info[i].getProductName() + "</td></tr>" + "<tr><td style='font-weight: bold;'>Version:</td><td>" +
+                            info[i].getProductVersion()
+                            .toString() +
+                            "</td></tr>"
+                            + "<tr><td style='font-weight: bold;'>Build Time:</td><td>" + DATE_FORMAT.format(
+                                new Date(info[i].getBuildTime())) + "</td></tr>"
+                            + "<tr><td style='font-weight: bold;'>License:</td><td>" + info[i].getLicense() + "</td></tr>" +
+                            "<tr><td style='font-weight: bold;'>Vendor:</td><td>" +
+                            info[i].getProductVendor()
+                            + "</td></tr>" + "<tr><td style='font-weight: bold;'>Homepage:</td><td>" + info[i].getHomepage() +
+                            "</td></tr>" +
+                            "</table></body></html>");
+      l.setFont(l.getFont().deriveFont(Font.PLAIN));
+      l.setBorder(new CompoundBorder(new EmptyBorder(0, 0, i == info.length - 1 ? 0 : 10, 0),
+                                     new TitledBorder(" " + info[i].getProductName() + " ")));
+      box.add(l);
+    }
+    return box;
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/RotatableLabel.java b/src/net/infonode/gui/RotatableLabel.java
new file mode 100644
index 0000000..58240f0
--- /dev/null
+++ b/src/net/infonode/gui/RotatableLabel.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RotatableLabel.java,v 1.8 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import javax.swing.plaf.LabelUI;
+import java.awt.*;
+
+public class RotatableLabel extends JLabel {
+  private RotatableLabelUI ui = new RotatableLabelUI(Direction.RIGHT);
+
+  public RotatableLabel(String text) {
+    super(text);
+    init();
+  }
+
+  public RotatableLabel(String text, Icon icon) {
+    super(text, icon, LEFT);
+    init();
+  }
+
+  private void init() {
+    super.setUI(ui);
+    super.setOpaque(false);
+  }
+
+  public Direction getDirection() {
+    return ui.getDirection();
+  }
+
+  public void setDirection(Direction direction) {
+    if (ui.getDirection() != direction) {
+      ui.setDirection(direction);
+      revalidate();
+    }
+  }
+
+  public void setMirror(boolean mirror) {
+    ui.setMirror(mirror);
+    revalidate();
+  }
+
+  public boolean isMirror() {
+    return ui.isMirror();
+  }
+
+  public void setUI(LabelUI ui) {
+    // Ignore
+  }
+
+  private boolean isVertical() {
+    return !ui.getDirection().isHorizontal();
+  }
+
+  private Dimension rotateDimension(Dimension dim) {
+    return dim == null ? null : isVertical() ? new Dimension(dim.height, dim.width) : dim;
+  }
+
+  public Dimension getPreferredSize() {
+    return rotateDimension(super.getPreferredSize());
+  }
+
+  public Dimension getMinimumSize() {
+    return rotateDimension(super.getMinimumSize());
+  }
+
+  public Dimension getMaximumSize() {
+    return rotateDimension(super.getMaximumSize());
+  }
+
+  public void setMinimumSize(Dimension minimumSize) {
+    super.setMinimumSize(rotateDimension(minimumSize));
+  }
+
+  public void setMaximumSize(Dimension maximumSize) {
+    super.setMaximumSize(rotateDimension(maximumSize));
+  }
+
+  public void setPreferredSize(Dimension preferredSize) {
+    super.setPreferredSize(rotateDimension(preferredSize));
+  }
+
+  public void setOpaque(boolean opaque) {
+  }
+
+}
diff --git a/src/net/infonode/gui/RotatableLabelUI.java b/src/net/infonode/gui/RotatableLabelUI.java
new file mode 100644
index 0000000..5ad043a
--- /dev/null
+++ b/src/net/infonode/gui/RotatableLabelUI.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RotatableLabelUI.java,v 1.11 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import javax.swing.plaf.basic.BasicLabelUI;
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+
+public class RotatableLabelUI extends BasicLabelUI {
+  // Optimization
+  private static Rectangle paintIconR = new Rectangle();
+  private static Rectangle paintTextR = new Rectangle();
+  private static Rectangle paintViewR = new Rectangle();
+
+  private Direction direction;
+  private boolean mirror;
+
+  public RotatableLabelUI(Direction direction) {
+    this(direction, false);
+  }
+
+  public RotatableLabelUI(Direction direction, boolean mirror) {
+    this.direction = direction;
+    this.mirror = mirror;
+  }
+
+  public Direction getDirection() {
+    return direction;
+  }
+
+  public void setDirection(Direction direction) {
+    this.direction = direction;
+  }
+
+  public boolean isMirror() {
+    return mirror;
+  }
+
+  public void setMirror(boolean mirror) {
+    this.mirror = mirror;
+  }
+
+  public void paint(Graphics g, JComponent c) {
+    JLabel label = (JLabel) c;
+    String text = label.getText();
+    Icon icon = (label.isEnabled()) ? label.getIcon() : label.getDisabledIcon();
+
+    if (icon == null && text == null)
+      return;
+
+    FontMetrics fm = g.getFontMetrics();
+    Insets insets = c.getInsets();
+
+    paintViewR.x = insets.left;
+    paintViewR.y = insets.top;
+
+    if (direction.isHorizontal()) {
+      paintViewR.height = c.getHeight() - (insets.top + insets.bottom);
+      paintViewR.width = c.getWidth() - (insets.left + insets.right);
+    }
+    else {
+      paintViewR.height = c.getWidth() - (insets.top + insets.bottom);
+      paintViewR.width = c.getHeight() - (insets.left + insets.right);
+    }
+
+    paintIconR.x = paintIconR.y = paintIconR.width = paintIconR.height = 0;
+    paintTextR.x = paintTextR.y = paintTextR.width = paintTextR.height = 0;
+
+    String clippedText = layoutCL(label, fm, text, icon, paintViewR, paintIconR, paintTextR);
+
+    Graphics2D g2 = (Graphics2D) g;
+    AffineTransform tr = g2.getTransform();
+
+    int m = mirror ? -1 : 1;
+    g2.transform(direction == Direction.RIGHT ? new AffineTransform(1, 0, 0, m, 0, mirror ? c.getHeight() : 0) :
+                 direction == Direction.DOWN ? new AffineTransform(0, 1, -m, 0, mirror ? 0 : c.getWidth(), 0) :
+                 direction == Direction.LEFT ? new AffineTransform(-1,
+                                                                   0,
+                                                                   0,
+                                                                   -m,
+                                                                   c.getWidth(),
+                                                                   mirror ? 0 : c.getHeight()) :
+                 new AffineTransform(0, -1, m, 0, mirror ? c.getWidth() : 0, c.getHeight()));
+
+    if (icon != null) {
+      icon.paintIcon(c, g, paintIconR.x, paintIconR.y);
+    }
+
+    if (text != null) {
+      int textX = paintTextR.x;
+      int textY = paintTextR.y + fm.getAscent();
+
+      if (label.isEnabled()) {
+        paintEnabledText(label, g, clippedText, textX, textY);
+      }
+      else {
+        paintDisabledText(label, g, clippedText, textX, textY);
+      }
+    }
+
+    g2.setTransform(tr);
+  }
+}
diff --git a/src/net/infonode/gui/ScrollButtonBox.java b/src/net/infonode/gui/ScrollButtonBox.java
new file mode 100644
index 0000000..5d6004a
--- /dev/null
+++ b/src/net/infonode/gui/ScrollButtonBox.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ScrollButtonBox.java,v 1.17 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+import net.infonode.gui.icon.button.ArrowIcon;
+import net.infonode.gui.layout.DirectionLayout;
+import net.infonode.gui.panel.SimplePanel;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.util.ArrayList;
+
+public class ScrollButtonBox extends SimplePanel {
+  private AbstractButton upButton;
+  private AbstractButton downButton;
+  private AbstractButton leftButton;
+  private AbstractButton rightButton;
+
+  private boolean button1Enabled;
+  private boolean button2Enabled;
+
+  private boolean vertical;
+
+  private ArrayList listeners;
+
+  private ActionListener button1Listener = new ActionListener() {
+    public void actionPerformed(ActionEvent e) {
+      fireButton1();
+    }
+  };
+
+  private ActionListener button2Listener = new ActionListener() {
+    public void actionPerformed(ActionEvent e) {
+      fireButton2();
+    }
+  };
+
+  public ScrollButtonBox(boolean vertical, int iconSize) {
+    this(vertical,
+         ButtonFactory.createFlatHighlightButton(new ArrowIcon(iconSize, Direction.UP), "", 0, null),
+         ButtonFactory.createFlatHighlightButton(new ArrowIcon(iconSize, Direction.DOWN), "", 0, null),
+         ButtonFactory.createFlatHighlightButton(new ArrowIcon(iconSize, Direction.LEFT), "", 0, null),
+         ButtonFactory.createFlatHighlightButton(new ArrowIcon(iconSize, Direction.RIGHT), "", 0, null));
+  }
+
+  public ScrollButtonBox(final boolean vertical,
+                         AbstractButton upButton,
+                         AbstractButton downButton,
+                         AbstractButton leftButton,
+                         AbstractButton rightButton) {
+    this.vertical = vertical;
+    setLayout(new DirectionLayout(vertical ? Direction.DOWN : Direction.RIGHT));
+
+    addMouseWheelListener(new MouseWheelListener() {
+      public void mouseWheelMoved(MouseWheelEvent e) {
+        if (e.getWheelRotation() < 0)
+          fireButton1();
+        else
+          fireButton2();
+      }
+    });
+
+    setButtons(upButton, downButton, leftButton, rightButton);
+  }
+
+  public void setButton1Enabled(boolean enabled) {
+    this.button1Enabled = enabled;
+    if (getComponentCount() > 0)
+      ((AbstractButton) getComponent(0)).setEnabled(enabled);
+  }
+
+  public void setButton2Enabled(boolean enabled) {
+    this.button2Enabled = enabled;
+    if (getComponentCount() > 0)
+      ((AbstractButton) getComponent(1)).setEnabled(enabled);
+  }
+
+  public boolean isButton1Enabled() {
+    return button1Enabled;
+  }
+
+  public boolean isButton2Enabled() {
+    return button2Enabled;
+  }
+
+  public void addListener(ScrollButtonBoxListener listener) {
+    if (listeners == null)
+      listeners = new ArrayList(2);
+
+    listeners.add(listener);
+  }
+
+  public void removeListener(ScrollButtonBoxListener listener) {
+    if (listeners != null) {
+      listeners.remove(listener);
+
+      if (listeners.size() == 0)
+        listeners = null;
+    }
+  }
+
+  public boolean isVertical() {
+    return vertical;
+  }
+
+  public void setVertical(boolean vertical) {
+    if (vertical != this.vertical) {
+      this.vertical = vertical;
+      initialize();
+      //update();
+    }
+  }
+
+  public void setButtons(AbstractButton upButton,
+                         AbstractButton downButton,
+                         AbstractButton leftButton,
+                         AbstractButton rightButton) {
+    if (upButton != this.upButton || downButton != this.downButton || leftButton != this.leftButton ||
+        rightButton != this.rightButton) {
+      this.upButton = upButton;
+      this.downButton = downButton;
+      this.leftButton = leftButton;
+      this.rightButton = rightButton;
+
+      initialize();
+    }
+  }
+
+  public AbstractButton getUpButton() {
+    return upButton;
+  }
+
+  public AbstractButton getDownButton() {
+    return downButton;
+  }
+
+  public AbstractButton getLeftButton() {
+    return leftButton;
+  }
+
+  public AbstractButton getRightButton() {
+    return rightButton;
+  }
+
+/*  public void updateUI() {
+    super.updateUI();
+
+    if (button1 != null) {
+      update();
+    }
+  }
+*/
+  private void fireButton1() {
+    if (listeners != null) {
+      Object[] l = listeners.toArray();
+
+      for (int i = 0; i < l.length; i++)
+        ((ScrollButtonBoxListener) l[i]).scrollButton1();
+    }
+  }
+
+  private void fireButton2() {
+    if (listeners != null) {
+      Object[] l = listeners.toArray();
+
+      for (int i = 0; i < l.length; i++)
+        ((ScrollButtonBoxListener) l[i]).scrollButton2();
+    }
+  }
+
+  private void initialize() {
+    if (getComponentCount() > 0) {
+      ((AbstractButton) getComponent(0)).removeActionListener(button1Listener);
+      ((AbstractButton) getComponent(1)).removeActionListener(button2Listener);
+      removeAll();
+    }
+
+    ((DirectionLayout) getLayout()).setDirection(vertical ? Direction.DOWN : Direction.RIGHT);
+
+    AbstractButton button1;
+    AbstractButton button2;
+
+    if (vertical) {
+      button1 = upButton;
+      button2 = downButton;
+    }
+    else {
+      button1 = leftButton;
+      button2 = rightButton;
+    }
+
+    if (button1 != null && button2 != null) {
+      add(button1);
+      add(button2);
+
+      button1.setFocusable(false);
+      button2.setFocusable(false);
+
+      button1.setEnabled(button1Enabled);
+      button2.setEnabled(button2Enabled);
+
+      button1.addActionListener(button1Listener);
+      button2.addActionListener(button2Listener);
+    }
+
+    if (getParent() != null)
+      ComponentUtil.validate(getParent());
+
+    //update();
+  }
+
+/*  private void update() {
+    Color c1 = UIManager.getColor("Button.foreground");
+    Color c2 = UIManager.getColor("Button.disabledForeground");
+
+    if (c2 == null)
+      c2 = ColorUtil.blend(c1, UIManager.getColor("Panel.background"), 0.7f);
+
+    button1.setIcon(new ArrowIcon(c1, iconSize, vertical ? Direction.UP : Direction.LEFT));
+    button2.setIcon(new ArrowIcon(c1, iconSize, vertical ? Direction.DOWN : Direction.RIGHT));
+
+    ArrowIcon icon = new ArrowIcon(c2, iconSize - 2, vertical ? Direction.UP : Direction.LEFT);
+    icon.setShadowEnabled(false);
+    button1.setDisabledIcon(new BorderIcon(icon, 1));
+
+    icon = new ArrowIcon(c2, iconSize - 2, vertical ? Direction.DOWN : Direction.RIGHT);
+    icon.setShadowEnabled(false);
+    button2.setDisabledIcon(new BorderIcon(icon, 1));
+  }*/
+}
diff --git a/src/net/infonode/gui/ScrollButtonBoxListener.java b/src/net/infonode/gui/ScrollButtonBoxListener.java
new file mode 100644
index 0000000..562f264
--- /dev/null
+++ b/src/net/infonode/gui/ScrollButtonBoxListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ScrollButtonBoxListener.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.gui;
+
+public interface ScrollButtonBoxListener {
+  void scrollButton1();
+
+  void scrollButton2();
+}
diff --git a/src/net/infonode/gui/ScrollableBox.java b/src/net/infonode/gui/ScrollableBox.java
new file mode 100644
index 0000000..51a3065
--- /dev/null
+++ b/src/net/infonode/gui/ScrollableBox.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+//$Id: ScrollableBox.java,v 1.27 2005/12/04 13:46:04 jesper Exp $
+
+package net.infonode.gui;
+
+import net.infonode.gui.layout.LayoutUtil;
+import net.infonode.gui.panel.SimplePanel;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+import java.util.ArrayList;
+
+public class ScrollableBox extends SimplePanel {
+  private LayoutManager l = new LayoutManager() {
+    public void addLayoutComponent(String name, Component comp) {
+    }
+
+    public void layoutContainer(Container parent) {
+      if (parent.getComponentCount() > 0) {
+        Component panel = parent.getComponent(0);
+
+        panel.setBounds(0, 0, panel.getPreferredSize().width, panel.getPreferredSize().height);
+        panel.validate();
+        update();
+      }
+    }
+
+    public Dimension minimumLayoutSize(Container parent) {
+      Dimension min = (parent.getComponentCount() == 0) ?
+                      new Dimension(0, 0) : parent.getComponent(0).getMinimumSize();
+
+      return LayoutUtil.add(vertical ? new Dimension(min.width, 0) : new Dimension(0, min.height), parent.getInsets());
+    }
+
+    public Dimension preferredLayoutSize(Container parent) {
+      return (parent.getComponentCount() == 0) ? new Dimension(0, 0) : parent.getComponent(0).getPreferredSize();
+    }
+
+    public void removeLayoutComponent(Component comp) {
+    }
+  };
+
+  private int leftIndex;
+  private boolean vertical;
+  private int scrollOffset;
+
+  private boolean leftEnd = true;
+  private boolean rightEnd = false;
+
+  private ArrayList layoutOrderList;
+
+  private MouseWheelListener mouseWheelListener = new MouseWheelListener() {
+    public void mouseWheelMoved(MouseWheelEvent e) {
+      setLeftIndex(leftIndex + e.getWheelRotation());
+    }
+  };
+
+  private ArrayList listeners = new ArrayList(1);
+
+  public ScrollableBox(final JComponent scrollingContainer, boolean vertical, int scrollOffset) {
+    setLayout(l);
+    this.vertical = vertical;
+    this.scrollOffset = scrollOffset;
+    add(scrollingContainer);
+
+    scrollingContainer.addMouseWheelListener(mouseWheelListener);
+    scrollingContainer.addHierarchyListener(new HierarchyListener() {
+      public void hierarchyChanged(HierarchyEvent e) {
+        if (scrollingContainer.getParent() != ScrollableBox.this) {
+          scrollingContainer.removeHierarchyListener(this);
+          scrollingContainer.removeMouseWheelListener(mouseWheelListener);
+        }
+      }
+    });
+  }
+
+  public void addScrollableBoxListener(ScrollableBoxListener listener) {
+    listeners.add(listener);
+  }
+
+  public void removeScrollableBoxListener(ScrollableBoxListener listener) {
+    listeners.remove(listener);
+  }
+
+  public void setScrollingContainer(final JComponent component) {
+  }
+
+  public JComponent getScrollingComponent() {
+    return (getComponentCount() == 0) ? null : (JComponent) getComponent(0);
+  }
+
+  public void scrollLeft(int numIndex) {
+    setLeftIndex(leftIndex - numIndex);
+  }
+
+  public void scrollRight(int numIndex) {
+    setLeftIndex(leftIndex + numIndex);
+  }
+
+  public void ensureVisible(int index) {
+    if (leftIndex > index) {
+      setLeftIndex(index);
+    }
+    else if (leftIndex < index) {
+      int newLeftIndex = findFitIndex(index);
+
+      if (newLeftIndex > leftIndex) {
+        setLeftIndex(newLeftIndex);
+      }
+    }
+  }
+
+  public boolean isLeftEnd() {
+    return leftEnd;
+  }
+
+  public boolean isRightEnd() {
+    return rightEnd;
+  }
+
+  public void setScrollOffset(int scrollOffset) {
+    if (scrollOffset != this.scrollOffset) {
+      this.scrollOffset = scrollOffset;
+      update();
+    }
+  }
+
+  public int getScrollOffset() {
+    return scrollOffset;
+  }
+
+  public boolean isVertical() {
+    return vertical;
+  }
+
+  public void setVertical(boolean vertical) {
+    this.vertical = vertical;
+    update();
+    fireChanged();
+  }
+
+  public void setLayoutOrderList(ArrayList layoutOrderList) {
+    this.layoutOrderList = layoutOrderList;
+  }
+
+  private int getDimensionSize(Dimension d) {
+    return (int) (vertical ? d.getHeight() : d.getWidth());
+  }
+
+  private Point createPos(int p) {
+    return vertical ? new Point(0, p) : new Point(p, 0);
+  }
+
+  private int getPos(Point p) {
+    return vertical ? p.y : p.x;
+  }
+
+  private int getScrollOffset(int index) {
+    if (index == 0)
+      return 0;
+
+    Component c = getScrollingComponents()[index - 1];
+    return Math.min(scrollOffset,
+                    Math.max(getDimensionSize(c.getMinimumSize()), getDimensionSize(c.getPreferredSize()) / 2));
+  }
+
+  private Component[] getScrollingComponents() {
+    JComponent c = getScrollingComponent();
+
+    if (c == null)
+      return new Component[0];
+
+    if (layoutOrderList != null) {
+      Component[] components = new Component[layoutOrderList.size()];
+      for (int i = 0; i < layoutOrderList.size(); i++)
+        components[i] = (Component) layoutOrderList.get(i);
+
+      return components;
+    }
+
+    return c.getComponents();
+  }
+
+  private int getScrollingComponentCount() {
+    JComponent c = getScrollingComponent();
+
+    return (c == null) ? 0 : c.getComponentCount();
+  }
+
+  private int findFitIndex(int lastIndex) {
+    int fitSize = getDimensionSize(getSize());
+
+    if ((fitSize == 0) || (lastIndex < 0)) {
+      return 0;
+    }
+
+    Component[] c = getScrollingComponents();
+    int endPos = getPos(c[lastIndex].getLocation()) + getDimensionSize(c[lastIndex].getSize());
+
+    for (int i = lastIndex; i >= 0; i--) {
+      if ((endPos - getPos(c[i].getLocation()) + getScrollOffset(i)) > fitSize) {
+        return Math.min(c.length - 1, i + 1);
+      }
+    }
+
+    return 0;
+  }
+
+  private void update() {
+    setLeftIndex(leftIndex);
+  }
+
+  private void setLeftIndex(int index) {
+    JComponent scrollingComponent = getScrollingComponent();
+
+    int oldLeftIndex = leftIndex;
+
+    if (scrollingComponent != null) {
+      int count = getScrollingComponentCount();
+      int fitIndex = findFitIndex(count - 1);
+      leftIndex = Math.min(fitIndex, Math.max(0, index));
+
+      leftEnd = leftIndex == 0;
+      rightEnd = !(leftIndex < fitIndex);
+
+      scrollingComponent.setLocation(createPos(((count == 0) ?
+                                                0 :
+                                                (-getPos(getScrollingComponents()[leftIndex].getLocation()))) + getScrollOffset(
+                                                    leftIndex)));
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        if (oldLeftIndex < index)
+          fireScrolledRight();
+        else if (oldLeftIndex > index)
+          fireScrolledLeft();
+    }
+  }
+
+  public void updateUI() {
+    super.updateUI();
+    if (listeners != null)
+      fireChanged();
+  }
+
+  private void fireScrolledLeft() {
+    Object[] l = listeners.toArray();
+    for (int i = 0; i < l.length; i++)
+      ((ScrollableBoxListener) l[i]).scrolledLeft(this);
+  }
+
+  private void fireScrolledRight() {
+    Object[] l = listeners.toArray();
+    for (int i = 0; i < l.length; i++)
+      ((ScrollableBoxListener) l[i]).scrolledRight(this);
+  }
+
+  private void fireChanged() {
+    Object[] l = listeners.toArray();
+    for (int i = 0; i < l.length; i++)
+      ((ScrollableBoxListener) l[i]).changed(this);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/ScrollableBoxListener.java b/src/net/infonode/gui/ScrollableBoxListener.java
new file mode 100644
index 0000000..6810205
--- /dev/null
+++ b/src/net/infonode/gui/ScrollableBoxListener.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ScrollableBoxListener.java,v 1.3 2004/09/22 14:35:05 jesper Exp $
+package net.infonode.gui;
+
+/**
+ * @author johan
+ */
+public interface ScrollableBoxListener {
+  public void scrolledLeft(ScrollableBox box);
+
+  public void scrolledRight(ScrollableBox box);
+
+  public void changed(ScrollableBox box);
+}
diff --git a/src/net/infonode/gui/SimpleSplitPane.java b/src/net/infonode/gui/SimpleSplitPane.java
new file mode 100644
index 0000000..982f723
--- /dev/null
+++ b/src/net/infonode/gui/SimpleSplitPane.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SimpleSplitPane.java,v 1.28 2005/12/04 13:46:04 jesper Exp $
+
+package net.infonode.gui;
+
+import net.infonode.gui.panel.BaseContainer;
+import net.infonode.gui.panel.SimplePanel;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.util.ArrayList;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.28 $
+ */
+public class SimpleSplitPane extends BaseContainer {
+  private LayoutManager splitLayout = new LayoutManager() {
+    public void addLayoutComponent(String name, Component comp) {
+    }
+
+    public void layoutContainer(Container parent) {
+      if (leftComponent == null || !leftComponent.isVisible()) {
+        maximize(rightComponent);
+      }
+      else if (rightComponent == null || !rightComponent.isVisible()) {
+        maximize(leftComponent);
+      }
+      else {
+        float dividerLocation = fixDividerLocation(getDividerLocation());
+        int totalSize = getViewSize();
+        int leftSize = (int) (totalSize * dividerLocation);
+        int otherSize = getOtherSize();
+        int offsetX = getInsets().left;
+        int offsetY = getInsets().top;
+
+        Dimension d = createSize(leftSize, otherSize);
+        leftComponent.setBounds(offsetX, offsetY, (int) d.getWidth(), (int) d.getHeight());
+
+        Point p = createPoint(leftSize, 0);
+        d = createSize(dividerSize, otherSize);
+        dividerPanel.setBounds(p.x + offsetX, p.y + offsetY, (int) d.getWidth(), (int) d.getHeight());
+
+        p = createPoint(leftSize + dividerSize, 0);
+        d = createSize(totalSize - leftSize, otherSize);
+        rightComponent.setBounds(p.x + offsetX, p.y + offsetY, (int) d.getWidth(), (int) d.getHeight());
+      }
+    }
+
+    private void maximize(Component component) {
+      if (component != null && component.isVisible()) {
+        Insets i = getInsets();
+        Dimension d = getSize();
+        component.setBounds(i.left, i.top, d.width - i.left - i.right, d.height - i.top - i.bottom);
+      }
+
+      dividerPanel.setBounds(0, 0, 0, 0);
+    }
+
+    public Dimension minimumLayoutSize(Container parent) {
+      Dimension d = createSize((leftComponent == null ? 0 : getDimensionSize(leftComponent.getMinimumSize())) + dividerSize
+                               + (rightComponent == null ? 0 : getDimensionSize(rightComponent.getMinimumSize())), Math.max(
+                                   leftComponent == null ? 0 : getOtherSize(leftComponent.getMinimumSize()), rightComponent == null
+                                                                                                             ?
+                                                                                                             0
+                                                                                                             :
+                                                                                                             getOtherSize(
+                                                                                                                 rightComponent.getMinimumSize())));
+      return new Dimension(d.width + getInsets().left + getInsets().right,
+                           d.height + getInsets().top + getInsets().bottom);
+    }
+
+    public Dimension preferredLayoutSize(Container parent) {
+      boolean lv = leftComponent != null && leftComponent.isVisible();
+      boolean rv = rightComponent != null && rightComponent.isVisible();
+      Dimension d = createSize(
+          (lv ? getDimensionSize(leftComponent.getPreferredSize()) : 0) + (lv && rv ? dividerSize : 0) + (rv ?
+                                                                                                          getDimensionSize(
+                                                                                                              rightComponent.getPreferredSize()) :
+                                                                                                          0),
+          Math.max(lv ? getOtherSize(leftComponent.getPreferredSize()) : 0,
+                   rv ? getOtherSize(rightComponent.getPreferredSize()) : 0));
+      return new Dimension(d.width + getInsets().left + getInsets().right,
+                           d.height + getInsets().top + getInsets().bottom);
+    }
+
+    public void removeLayoutComponent(Component comp) {
+    }
+  };
+
+  private Component leftComponent;
+  private Component rightComponent;
+  private SimplePanel dividerPanel = new SimplePanel();
+  private Component dragIndicator;
+  private boolean dividerDraggable = true;
+  private boolean continuousLayout = true;
+  private float dragLocation;
+  private boolean horizontal;
+  private float dividerLocation = 0.5F;
+  private int dividerSize = 6;
+  private ArrayList listeners = new ArrayList(0);
+  private Color dragIndicatorColor = Color.DARK_GRAY;
+
+  public SimpleSplitPane(boolean horizontal) {
+    this(horizontal, false);
+  }
+
+  public SimpleSplitPane(boolean horizontal, boolean heavyWeightDragIndicator) {
+    setLayout(splitLayout);
+    add(dividerPanel);
+    setHorizontal(horizontal);
+
+    setHeavyWeightDragIndicator(heavyWeightDragIndicator);
+
+    dividerPanel.addMouseListener(new MouseAdapter() {
+      public void mousePressed(MouseEvent e) {
+        if (e.getButton() == MouseEvent.BUTTON1) {// &&
+          // MouseEventCoalesceManager.getInstance().isPressedAllowed(e))
+          // {
+          CursorManager.setGlobalCursor(getRootPane(), dividerPanel.getCursor());
+          if (dividerDraggable && !continuousLayout) {
+            float location = (float) (getPos(dividerPanel.getLocation()) - getOffset() + getPos(e.getPoint())) / getViewSize();
+            setDragIndicator(location);
+          }
+        }
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        if (e.getButton() == MouseEvent.BUTTON1) {// &&
+          // MouseEventCoalesceManager.getInstance().isReleasedAllowed(e))
+          // {
+          CursorManager.resetGlobalCursor(getRootPane());
+
+          if (dividerDraggable && !continuousLayout) {
+            dragIndicator.setVisible(false);
+            setDividerLocation(dragLocation);
+          }
+        }
+      }
+    });
+
+    dividerPanel.addMouseMotionListener(new MouseMotionAdapter() {
+      public void mouseDragged(MouseEvent e) {
+        if (dividerDraggable && /* MouseEventCoalesceManager.getInstance().isDraggedAllowed(e) && */(e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0) {
+          float location = (float) (getPos(dividerPanel.getLocation()) - getOffset() + getPos(e.getPoint())) / getViewSize();
+
+          if (continuousLayout)
+            setDividerLocation(location);
+          else
+            setDragIndicator(location);
+        }
+      }
+    });
+  }
+
+  public SimpleSplitPane(boolean horizontal, Component leftComponent, Component rightComponent) {
+    this(horizontal);
+    setLeftComponent(leftComponent);
+    setRightComponent(rightComponent);
+  }
+
+  public void addListener(SimpleSplitPaneListener listener) {
+    ArrayList newListeners = new ArrayList(listeners.size() + 1);
+    newListeners.addAll(listeners);
+    listeners = newListeners;
+    listeners.add(listener);
+  }
+
+  public JComponent getDividerPanel() {
+    return dividerPanel;
+  }
+
+  public boolean isDividerDraggable() {
+    return dividerDraggable;
+  }
+
+  public void setDividerDraggable(boolean dividerDraggable) {
+    this.dividerDraggable = dividerDraggable;
+    updateDividerCursor();
+  }
+
+  public void setHeavyWeightDragIndicator(boolean heavyWeight) {
+    initDragIndicatior(heavyWeight);
+  }
+
+  public Color getDragIndicatorColor() {
+    return dragIndicatorColor;
+  }
+
+  public void setDragIndicatorColor(Color dragIndicatorColor) {
+    this.dragIndicatorColor = dragIndicatorColor;
+    dragIndicator.setBackground(dragIndicatorColor);
+  }
+
+  private void setDragIndicator(float location) {
+    dragLocation = fixDividerLocation(location);
+    dragIndicator.setVisible(true);
+    Point p = createPoint((int) (getViewSize() * dragLocation), 0);
+    Dimension d = createSize(dividerSize, getOtherSize());
+    dragIndicator.setBounds((int) (p.getX() + getInsets().left),
+                            (int) (p.getY() + getInsets().top),
+                            (int) d.getWidth(),
+                            (int) d.getHeight());
+  }
+
+
+  private void initDragIndicatior(boolean heavyWeight) {
+    if (dragIndicator != null)
+      remove(dragIndicator);
+
+    if (heavyWeight) {
+      dragIndicator = new Canvas();
+    }
+    else {
+      dragIndicator = new BaseContainer();
+    }
+
+    add(dragIndicator, 0);
+    dragIndicator.setBackground(dragIndicatorColor);
+    dragIndicator.setVisible(false);
+  }
+
+  private float fixDividerLocation(float location) {
+    int totalSize = getViewSize();
+
+    if (totalSize <= 0)
+      return 0.5F;
+
+    int leftSize = Math.max((int) (totalSize * location),
+                            leftComponent == null || !leftComponent.isVisible() ?
+                            0 : getDimensionSize(leftComponent.getMinimumSize()));
+    leftSize = Math.min(leftSize,
+                        totalSize - (rightComponent == null || !rightComponent.isVisible() ?
+                                     0 : getDimensionSize(rightComponent.getMinimumSize())));
+    return (float) leftSize / totalSize;
+  }
+
+  public void setContinuousLayout(boolean value) {
+    continuousLayout = value;
+  }
+
+  public boolean isContinuousLayout() {
+    return continuousLayout;
+  }
+
+  public int getDividerSize() {
+    return dividerSize;
+  }
+
+  public void setDividerSize(int dividerSize) {
+    this.dividerSize = dividerSize;
+    revalidate();
+  }
+
+  private int getOffset() {
+    return horizontal ? getInsets().left : getInsets().top;
+  }
+
+  private int getOtherSize() {
+    return horizontal ?
+           getHeight() - getInsets().top - getInsets().bottom : getWidth() - getInsets().left - getInsets().right;
+  }
+
+  private int getViewSize() {
+    return getDimensionSize(getSize()) - dividerSize - (horizontal ?
+                                                        getInsets().left + getInsets().right :
+                                                        getInsets().top + getInsets().bottom);
+  }
+
+  private int getDimensionSize(Dimension d) {
+    return (int) (horizontal ? d.getWidth() : d.getHeight());
+  }
+
+  private int getOtherSize(Dimension d) {
+    return (int) (horizontal ? d.getHeight() : d.getWidth());
+  }
+
+  private int getPos(Point p) {
+    return (int) (horizontal ? p.getX() : p.getY());
+  }
+
+  private Dimension createSize(int size, int otherSize) {
+    return horizontal ? new Dimension(size, otherSize) : new Dimension(otherSize, size);
+  }
+
+  private Point createPoint(int pos, int otherPos) {
+    return horizontal ? new Point(pos, otherPos) : new Point(otherPos, pos);
+  }
+
+  public boolean isHorizontal() {
+    return horizontal;
+  }
+
+  public void setHorizontal(boolean horizontal) {
+    this.horizontal = horizontal;
+    updateDividerCursor();
+    revalidate();
+  }
+
+  public float getDividerLocation() {
+    return dividerLocation;
+  }
+
+  public void setDividerLocation(float dividerLocation) {
+    this.dividerLocation = dividerLocation;
+    revalidate();
+
+    for (int i = 0; i < listeners.size(); i++)
+      ((SimpleSplitPaneListener) listeners.get(i)).dividerLocationChanged(this);
+  }
+
+  public Component getLeftComponent() {
+    return leftComponent;
+  }
+
+  public void setLeftComponent(Component leftComponent) {
+    if (this.leftComponent != null)
+      remove(this.leftComponent);
+
+    this.leftComponent = leftComponent;
+
+    if (leftComponent != null)
+      add(leftComponent);
+
+    revalidate();
+  }
+
+  public Component getRightComponent() {
+    return rightComponent;
+  }
+
+  public void setRightComponent(Component rightComponent) {
+    if (this.rightComponent != null)
+      remove(this.rightComponent);
+
+    this.rightComponent = rightComponent;
+
+    if (rightComponent != null)
+      add(rightComponent);
+
+    revalidate();
+  }
+
+  private void updateDividerCursor() {
+    dividerPanel.setCursor(
+        dividerDraggable ?
+        new Cursor(horizontal ? Cursor.W_RESIZE_CURSOR : Cursor.N_RESIZE_CURSOR) : Cursor.getDefaultCursor());
+  }
+
+  public void setComponents(Component leftComponent, Component rightComponent) {
+    setLeftComponent(leftComponent);
+    setRightComponent(rightComponent);
+  }
+}
diff --git a/src/net/infonode/gui/SimpleSplitPaneListener.java b/src/net/infonode/gui/SimpleSplitPaneListener.java
new file mode 100644
index 0000000..f6f048d
--- /dev/null
+++ b/src/net/infonode/gui/SimpleSplitPaneListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SimpleSplitPaneListener.java,v 1.2 2004/12/17 16:57:25 jesper Exp $
+package net.infonode.gui;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ */
+public interface SimpleSplitPaneListener {
+  void dividerLocationChanged(SimpleSplitPane simpleSplitPane);
+}
diff --git a/src/net/infonode/gui/TextIconListCellRenderer.java b/src/net/infonode/gui/TextIconListCellRenderer.java
new file mode 100644
index 0000000..cf6e1c4
--- /dev/null
+++ b/src/net/infonode/gui/TextIconListCellRenderer.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TextIconListCellRenderer.java,v 1.11 2006/06/13 20:12:39 johan Exp $
+package net.infonode.gui;
+
+import net.infonode.gui.icon.IconUtil;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author johan
+ */
+public class TextIconListCellRenderer extends DefaultListCellRenderer {
+  private ListCellRenderer renderer;
+  private Icon emptyIcon;
+  private int width;
+  private int gap = -1;
+
+  public TextIconListCellRenderer(ListCellRenderer renderer) {
+    this.renderer = renderer;
+  }
+
+  public void calculateMaximumIconWidth(Object[] list) {
+    width = IconUtil.getMaxIconWidth(list);
+    emptyIcon = width == 0 ? null : new Icon() {
+      public int getIconHeight() {
+        return 1;
+      }
+
+      public int getIconWidth() {
+        return width;
+      }
+
+      public void paintIcon(Component c, Graphics g, int x, int y) {
+      }
+    };
+  }
+
+  public void setRenderer(ListCellRenderer renderer) {
+    this.renderer = renderer;
+  }
+
+  public Component getListCellRendererComponent(JList list,
+                                                Object value,
+                                                int index,
+                                                boolean isSelected,
+                                                boolean cellHasFocus) {
+    if (index == -1)
+      return null;
+
+    JLabel label = (JLabel) renderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+    if (gap < 0)
+      gap = label.getIconTextGap();
+    
+    Icon icon = IconUtil.getIcon(value);
+
+    if (icon == null) {
+      label.setIcon(emptyIcon);
+    }
+    else {
+      label.setIcon(icon);
+      label.setIconTextGap(gap + width - icon.getIconWidth());
+    }
+
+    return label;
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/TranslatingShape.java b/src/net/infonode/gui/TranslatingShape.java
new file mode 100644
index 0000000..f0587af
--- /dev/null
+++ b/src/net/infonode/gui/TranslatingShape.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TranslatingShape.java,v 1.4 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.PathIterator;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class TranslatingShape implements Shape {
+  private Shape shape;
+  private double dx;
+  private double dy;
+
+  public TranslatingShape(Shape shape, double dx, double dy) {
+    this.shape = shape;
+    this.dx = dx;
+    this.dy = dy;
+  }
+
+  public Rectangle getBounds() {
+    Rectangle r = shape.getBounds();
+    r.translate((int) dx, (int) dy);
+    return r;
+  }
+
+  public Rectangle2D getBounds2D() {
+    Rectangle2D r = shape.getBounds2D();
+    r.setRect(r.getMinX() + dx, r.getMinY() + dy, r.getWidth(), r.getHeight());
+    return r;
+  }
+
+  public boolean contains(double x, double y) {
+    return shape.contains(x - dx, y - dy);
+  }
+
+  public boolean contains(Point2D p) {
+    return contains(p.getX(), p.getY());
+  }
+
+  public boolean intersects(double x, double y, double w, double h) {
+    return shape.intersects(x - dx, y - dy, w, h);
+  }
+
+  public boolean intersects(Rectangle2D r) {
+    return intersects(r.getMinX(), r.getMinY(), r.getWidth(), r.getHeight());
+  }
+
+  public boolean contains(double x, double y, double w, double h) {
+    return shape.contains(x - dx, y - dy, w, h);
+  }
+
+  public boolean contains(Rectangle2D r) {
+    return contains(r.getMinX() - dx, r.getMinY() - dy, r.getWidth(), r.getHeight());
+  }
+
+  public PathIterator getPathIterator(AffineTransform at) {
+    return new Iterator(shape.getPathIterator(at));
+  }
+
+  public PathIterator getPathIterator(AffineTransform at, double flatness) {
+    return new Iterator(shape.getPathIterator(at, flatness));
+  }
+
+  private class Iterator implements PathIterator {
+    private PathIterator iterator;
+
+    Iterator(PathIterator iterator) {
+      this.iterator = iterator;
+    }
+
+    public int getWindingRule() {
+      return iterator.getWindingRule();
+    }
+
+    public boolean isDone() {
+      return iterator.isDone();
+    }
+
+    public void next() {
+      iterator.next();
+    }
+
+    public int currentSegment(float[] coords) {
+      int result = iterator.currentSegment(coords);
+
+      for (int i = 0; i < coords.length; i++) {
+        coords[i++] += dx;
+        coords[i] += dy;
+      }
+
+      return result;
+    }
+
+    public int currentSegment(double[] coords) {
+      int result = iterator.currentSegment(coords);
+
+      for (int i = 0; i < coords.length; i++) {
+        coords[i++] += dx;
+        coords[i] += dy;
+      }
+
+      return result;
+    }
+  }
+}
diff --git a/src/net/infonode/gui/UIManagerUtil.java b/src/net/infonode/gui/UIManagerUtil.java
new file mode 100644
index 0000000..cae7e39
--- /dev/null
+++ b/src/net/infonode/gui/UIManagerUtil.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: UIManagerUtil.java,v 1.6 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui;
+
+import net.infonode.gui.border.BorderUtil;
+import net.infonode.util.ColorUtil;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class UIManagerUtil {
+  private UIManagerUtil() {
+  }
+
+  public static Insets getInsets(String key) {
+    return InsetsUtil.copy(UIManager.getInsets(key));
+  }
+
+  public static Insets getInsets(String key, Insets insets) {
+    Insets i = getInsets(key);
+    return i == null ? insets : i;
+  }
+
+  public static Insets getInsets(String key, String defaultKey) {
+    Insets i = getInsets(key);
+
+    if (i != null)
+      return i;
+
+    i = getInsets(defaultKey);
+    return i == null ? new Insets(0, 0, 0, 0) : i;
+  }
+
+  public static Color getColor(String key) {
+    return ColorUtil.copy(UIManager.getColor(key));
+  }
+
+  public static Color getColor(String key, String defaultKey) {
+    return getColor(key, defaultKey, Color.BLACK);
+  }
+
+  public static Color getColor(String key, String defaultKey, Color defaultColor) {
+    Color i = getColor(key);
+
+    if (i != null)
+      return i;
+
+    i = getColor(defaultKey);
+    return i == null ? defaultColor : i;
+  }
+
+  public static Border getBorder(String key) {
+    return BorderUtil.copy(UIManager.getBorder(key));
+  }
+
+  public static Border getBorder(String key, String defaultKey) {
+    Border i = getBorder(key);
+
+    if (i != null)
+      return i;
+
+    i = getBorder(defaultKey);
+    return i == null ? BorderFactory.createEmptyBorder() : i;
+  }
+
+  public static Font getFont(String key) {
+    Font font = UIManager.getFont(key);
+    if (font == null)
+      font = new JLabel().getFont();
+    return FontUtil.copy(font);
+  }
+
+  public static Font getFont(String key, String defaultKey) {
+    Font i = getFont(key);
+
+    if (i != null)
+      return i;
+
+    i = getFont(defaultKey);
+    return i == null ? new Font("Dialog", 0, 11) : i;
+  }
+
+  public static Color getColor(String key, Color defaultColor) {
+    Color c = getColor(key);
+    return c == null ? defaultColor : c;
+  }
+}
diff --git a/src/net/infonode/gui/action/SimpleAction.java b/src/net/infonode/gui/action/SimpleAction.java
new file mode 100644
index 0000000..3b42ee1
--- /dev/null
+++ b/src/net/infonode/gui/action/SimpleAction.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SimpleAction.java,v 1.4 2005/02/16 11:28:10 jesper Exp $
+package net.infonode.gui.action;
+
+import net.infonode.gui.icon.IconProvider;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+
+/**
+ * An action with an icon and a title.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.3.0
+ */
+abstract public class SimpleAction implements IconProvider {
+  abstract public String getName();
+
+  abstract public void perform();
+
+  abstract public boolean isEnabled();
+
+  protected SimpleAction() {
+  }
+
+  /**
+   * Converts this action into a Swing {@link Action}.
+   *
+   * @return the Swing {@link Action}
+   */
+  public Action toSwingAction() {
+    AbstractAction action = new AbstractAction(getName(), getIcon()) {
+      public void actionPerformed(ActionEvent e) {
+        perform();
+      }
+    };
+    action.setEnabled(isEnabled());
+    return action;
+  }
+}
diff --git a/src/net/infonode/gui/action/package.html b/src/net/infonode/gui/action/package.html
new file mode 100644
index 0000000..91b9afa
--- /dev/null
+++ b/src/net/infonode/gui/action/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Action related classes.
+</body>
+</html>
diff --git a/src/net/infonode/gui/border/BorderUtil.java b/src/net/infonode/gui/border/BorderUtil.java
new file mode 100644
index 0000000..000b1d7
--- /dev/null
+++ b/src/net/infonode/gui/border/BorderUtil.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BorderUtil.java,v 1.6 2005/02/16 11:28:10 jesper Exp $
+package net.infonode.gui.border;
+
+import net.infonode.gui.InsetsUtil;
+
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class BorderUtil {
+  private BorderUtil() {
+  }
+
+  public static Insets getInsetsOutside(Component c, Border border, Border outside) {
+    Insets insets = new Insets(0, 0, 0, 0);
+    getInsetsOutside(c, border, outside, insets);
+    return insets;
+  }
+
+  private static boolean getInsetsOutside(Component c, Border border, Border outside, Insets insets) {
+    if (border == null)
+      return false;
+    else if (border == outside)
+      return true;
+    else if (border instanceof CompoundBorder) {
+      CompoundBorder b = (CompoundBorder) border;
+      return getInsetsOutside(c, b.getOutsideBorder(), outside, insets) ||
+             getInsetsOutside(c, b.getInsideBorder(), outside, insets);
+    }
+    else {
+      InsetsUtil.addTo(insets, border.getBorderInsets(c));
+      return false;
+    }
+  }
+
+  public static Border copy(final Border border) {
+    return new Border() {
+      public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+        border.paintBorder(c, g, x, y, width, height);
+      }
+
+      public Insets getBorderInsets(Component c) {
+        return border.getBorderInsets(c);
+      }
+
+      public boolean isBorderOpaque() {
+        return border.isBorderOpaque();
+      }
+    };
+  }
+}
diff --git a/src/net/infonode/gui/border/EdgeBorder.java b/src/net/infonode/gui/border/EdgeBorder.java
new file mode 100644
index 0000000..3febc12
--- /dev/null
+++ b/src/net/infonode/gui/border/EdgeBorder.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: EdgeBorder.java,v 1.14 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.border;
+
+import net.infonode.gui.ComponentUtil;
+import net.infonode.gui.GraphicsUtil;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.FixedColorProvider;
+import net.infonode.util.ColorUtil;
+
+import javax.swing.border.Border;
+import java.awt.*;
+import java.io.Serializable;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.14 $
+ */
+public class EdgeBorder implements Border, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private ColorProvider topLeftColor;
+  private ColorProvider bottomRightColor;
+  private boolean drawTop;
+  private boolean drawBottom;
+  private boolean drawLeft;
+  private boolean drawRight;
+  private Insets insets;
+
+  public EdgeBorder() {
+    this(true, true, true, true);
+  }
+
+  public EdgeBorder(boolean drawTop, boolean drawBottom, boolean drawLeft, boolean drawRight) {
+    this(null, drawTop, drawBottom, drawLeft, drawRight);
+  }
+
+  public EdgeBorder(Color color, boolean drawTop, boolean drawBottom, boolean drawLeft, boolean drawRight) {
+    ColorProvider c = color == null ? null : new FixedColorProvider(color);
+    init(c, c, drawTop, drawBottom, drawLeft, drawRight);
+  }
+
+  public EdgeBorder(ColorProvider color) {
+    init(color, color, true, true, true, true);
+  }
+
+  public EdgeBorder(ColorProvider topLeftColor,
+                    ColorProvider bottomRightColor,
+                    boolean drawTop,
+                    boolean drawBottom,
+                    boolean drawLeft,
+                    boolean drawRight) {
+    init(topLeftColor, bottomRightColor, drawTop, drawBottom, drawLeft, drawRight);
+  }
+
+  private void init(ColorProvider topLeftColor,
+                    ColorProvider bottomRightColor,
+                    boolean drawTop,
+                    boolean drawBottom,
+                    boolean drawLeft,
+                    boolean drawRight) {
+    this.topLeftColor = topLeftColor;
+    this.bottomRightColor = bottomRightColor;
+    this.drawTop = drawTop;
+    this.drawBottom = drawBottom;
+    this.drawLeft = drawLeft;
+    this.drawRight = drawRight;
+    insets = new Insets(drawTop ? 1 : 0, drawLeft ? 1 : 0, drawBottom ? 1 : 0, drawRight ? 1 : 0);
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    Color topLeft = getColor(topLeftColor, c);
+    Color bottomRight = getColor(bottomRightColor, c);
+
+    if (topLeft != null && bottomRight != null) {
+      g.setColor(topLeft);
+
+      if (drawTop)
+        GraphicsUtil.drawOptimizedLine(g, x, y, x + width - 1, y);
+
+      if (drawLeft)
+        GraphicsUtil.drawOptimizedLine(g, x, y, x, y + height - 1);
+
+      g.setColor(bottomRight);
+      if (drawRight)
+        GraphicsUtil.drawOptimizedLine(g, x + width - 1, y, x + width - 1, y + height - 1);
+
+      if (drawBottom)
+        GraphicsUtil.drawOptimizedLine(g, x, y + height - 1, x + width - 1, y + height - 1);
+    }
+  }
+
+  public Insets getBorderInsets(Component c) {
+    return insets;
+  }
+
+  public boolean isBorderOpaque() {
+    return false;
+  }
+
+  private Color getColor(ColorProvider color, Component c) {
+    Color col = color != null ? color.getColor() : null;
+
+    if (col == null) {
+      Color background = ComponentUtil.getBackgroundColor(c);
+      return background == null ? null : ColorUtil.mult(background, 0.7f);
+    }
+
+    return col;
+  }
+}
diff --git a/src/net/infonode/gui/border/EtchedLineBorder.java b/src/net/infonode/gui/border/EtchedLineBorder.java
new file mode 100644
index 0000000..b0667ba
--- /dev/null
+++ b/src/net/infonode/gui/border/EtchedLineBorder.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: EtchedLineBorder.java,v 1.10 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.border;
+
+import net.infonode.gui.ComponentUtil;
+import net.infonode.gui.GraphicsUtil;
+import net.infonode.util.ColorUtil;
+
+import javax.swing.border.Border;
+import java.awt.*;
+import java.io.Serializable;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ */
+public class EtchedLineBorder implements Border, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private boolean drawTop;
+  private boolean drawBottom;
+  private boolean drawLeft;
+  private boolean drawRight;
+  private Insets insets;
+  private Color highlightColor;
+  private Color shadowColor;
+
+  public EtchedLineBorder() {
+    this(true, true, true, true);
+  }
+
+  public EtchedLineBorder(boolean drawTop, boolean drawLeft, boolean drawBottom, boolean drawRight) {
+    this(drawTop, drawLeft, drawBottom, drawRight, null, null);
+  }
+
+  public EtchedLineBorder(boolean drawTop, boolean drawLeft, boolean drawBottom, boolean drawRight,
+                          Color highlightColor, Color shadowColor) {
+    this.drawBottom = drawBottom;
+    this.drawLeft = drawLeft;
+    this.drawRight = drawRight;
+    this.drawTop = drawTop;
+    insets = new Insets(drawTop ? 2 : 0, drawLeft ? 2 : 0, drawBottom ? 2 : 0, drawRight ? 2 : 0);
+    this.highlightColor = highlightColor;
+    this.shadowColor = shadowColor;
+  }
+
+  public Insets getBorderInsets(Component c) {
+    return insets;
+  }
+
+  public boolean isBorderOpaque() {
+    return false;
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    Color c1 = highlightColor == null ? ColorUtil.mult(ComponentUtil.getBackgroundColor(c), 1.70) : highlightColor;
+    Color c2 = shadowColor == null ? ColorUtil.mult(ComponentUtil.getBackgroundColor(c), 0.5) : shadowColor;
+    g.setColor(c1);
+
+    if (drawTop)
+      GraphicsUtil.drawOptimizedLine(g, x, y + 1, x + width - 1, y + 1);
+
+    if (drawLeft)
+      GraphicsUtil.drawOptimizedLine(g, x + 1, y, x + 1, y + height - 1);
+
+    g.setColor(c2);
+
+    if (drawBottom)
+      GraphicsUtil.drawOptimizedLine(g, x, y + height - 2, x + width - 1, y + height - 2);
+
+    if (drawRight)
+      GraphicsUtil.drawOptimizedLine(g, x + width - 2, y, x + width - 2, y + height - 1);
+
+    g.setColor(c1);
+
+    if (drawBottom)
+      GraphicsUtil.drawOptimizedLine(g, x, y + height - 1, x + width - 1, y + height - 1);
+
+    if (drawRight)
+      GraphicsUtil.drawOptimizedLine(g, x + width - 1, y, x + width - 1, y + height - 1);
+
+    g.setColor(c2);
+
+    if (drawTop)
+      GraphicsUtil.drawOptimizedLine(g, x, y, x + width - 1, y);
+
+    if (drawLeft)
+      GraphicsUtil.drawOptimizedLine(g, x, y, x, y + height - 1);
+
+    g.setColor(ComponentUtil.getBackgroundColor(c));
+
+    if (drawTop && drawRight)
+      GraphicsUtil.drawOptimizedLine(g, x + width - 2, y + 1, x + width - 1, y);
+
+    if (drawBottom && drawLeft)
+      GraphicsUtil.drawOptimizedLine(g, x, y + height - 1, x + 1, y + height - 2);
+  }
+}
diff --git a/src/net/infonode/gui/border/FocusBorder.java b/src/net/infonode/gui/border/FocusBorder.java
new file mode 100644
index 0000000..e5e91b3
--- /dev/null
+++ b/src/net/infonode/gui/border/FocusBorder.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FocusBorder.java,v 1.15 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.gui.border;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Insets;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.io.Serializable;
+
+import javax.swing.UIManager;
+import javax.swing.border.Border;
+import javax.swing.plaf.basic.BasicGraphicsUtils;
+
+import net.infonode.gui.UIManagerUtil;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.15 $
+ */
+public class FocusBorder implements Border, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private static final Insets INSETS = new Insets(1, 1, 1, 1);
+
+  private final Component component;
+
+  private boolean enabled = true;
+
+  public FocusBorder(final Component focusComponent) {
+    this.component = focusComponent;
+    focusComponent.addFocusListener(new FocusListener() {
+      public void focusGained(FocusEvent e) {
+        if (enabled)
+          focusComponent.repaint();
+      }
+
+      public void focusLost(FocusEvent e) {
+        if (enabled)
+          focusComponent.repaint();
+      }
+    });
+  }
+
+  public Insets getBorderInsets(Component c) {
+    return INSETS;
+  }
+
+  public boolean isBorderOpaque() {
+    return false;
+  }
+
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  public void setEnabled(boolean enabled) {
+    if (enabled != this.enabled) {
+      this.enabled = enabled;
+    }
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    if (enabled && component.hasFocus()) {
+      g.setColor(UIManagerUtil.getColor("Button.focus", "TabbedPane.focus"));
+
+      if (UIManager.getLookAndFeel().getClass().getName().equals("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"))
+        BasicGraphicsUtils.drawDashedRect(g, x, y, width, height);
+      else
+        g.drawRect(x, y, width - 1, height - 1);
+    }
+  }
+}
diff --git a/src/net/infonode/gui/border/HighlightBorder.java b/src/net/infonode/gui/border/HighlightBorder.java
new file mode 100644
index 0000000..b317c37
--- /dev/null
+++ b/src/net/infonode/gui/border/HighlightBorder.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: HighlightBorder.java,v 1.17 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.border;
+
+import net.infonode.gui.GraphicsUtil;
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.colorprovider.BackgroundPainterColorProvider;
+import net.infonode.gui.colorprovider.ColorMultiplier;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.ColorProviderUtil;
+import net.infonode.util.Direction;
+
+import javax.swing.border.Border;
+import java.awt.*;
+import java.io.Serializable;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.17 $
+ */
+public class HighlightBorder implements Border, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private static final Insets INSETS = new Insets(1, 1, 0, 0);
+  private boolean lowered;
+  private boolean pressed;
+  private ColorProvider colorProvider;
+
+  public HighlightBorder() {
+    this(false);
+  }
+
+  public HighlightBorder(boolean lowered) {
+    this(lowered, null);
+  }
+
+  public HighlightBorder(boolean lowered, Color color) {
+    this(lowered, false, color);
+  }
+
+  public HighlightBorder(boolean lowered, boolean pressed, Color color) {
+    this(lowered, pressed, ColorProviderUtil.getColorProvider(color,
+                                                              new ColorMultiplier(
+                                                                  BackgroundPainterColorProvider.INSTANCE,
+                                                                  lowered ? 0.7 : 1.70)));
+  }
+
+  public HighlightBorder(boolean lowered, boolean pressed, ColorProvider colorProvider) {
+    this.lowered = lowered;
+    this.pressed = pressed;
+    this.colorProvider = colorProvider;
+  }
+
+  public Insets getBorderInsets(Component c) {
+    return pressed ? InsetsUtil.rotate(Direction.LEFT, INSETS) : INSETS;
+  }
+
+  public boolean isBorderOpaque() {
+    return false;
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    g.setColor(colorProvider.getColor(c));
+
+    if (pressed) {
+      GraphicsUtil.drawOptimizedLine(g, x + (lowered ? 0 : 1), y + height - 1, x + width - 1, y + height - 1);
+      GraphicsUtil.drawOptimizedLine(g, x + width - 1, y + (lowered ? 0 : 1), x + width - 1, y + height - 2);
+    }
+    else {
+      GraphicsUtil.drawOptimizedLine(g, x, y, x + width - (lowered ? 1 : 2), y);
+      GraphicsUtil.drawOptimizedLine(g, x, y, x, y + height - (lowered ? 1 : 2));
+    }
+  }
+}
diff --git a/src/net/infonode/gui/border/PopupMenuBorder.java b/src/net/infonode/gui/border/PopupMenuBorder.java
new file mode 100644
index 0000000..e521422
--- /dev/null
+++ b/src/net/infonode/gui/border/PopupMenuBorder.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PopupMenuBorder.java,v 1.7 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.border;
+
+import net.infonode.gui.GraphicsUtil;
+
+import javax.swing.border.Border;
+import java.awt.*;
+import java.io.Serializable;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class PopupMenuBorder implements Border, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private static final Insets INSETS = new Insets(3, 1, 2, 1);
+
+  private Color highlightColor;
+  private Color shadowColor;
+
+  public PopupMenuBorder(Color highlightColor, Color shadowColor) {
+    this.highlightColor = highlightColor;
+    this.shadowColor = shadowColor;
+  }
+
+  public boolean isBorderOpaque() {
+    return false;
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    g.setColor(shadowColor);
+    g.drawRect(x, y, width - 1, height - 1);
+
+    g.setColor(highlightColor);
+    GraphicsUtil.drawOptimizedLine(g, x + 1, y + 1, x + width - 2, y + 1);
+    GraphicsUtil.drawOptimizedLine(g, x + 1, y + 2, x + 1, y + 2);
+    GraphicsUtil.drawOptimizedLine(g, x + 1, y + height - 2, x + 1, y + height - 2);
+  }
+
+  public Insets getBorderInsets(Component c) {
+    return INSETS;
+  }
+}
diff --git a/src/net/infonode/gui/button/ButtonFactory.java b/src/net/infonode/gui/button/ButtonFactory.java
new file mode 100644
index 0000000..18aacc7
--- /dev/null
+++ b/src/net/infonode/gui/button/ButtonFactory.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ButtonFactory.java,v 1.5 2005/02/16 11:28:10 jesper Exp $
+package net.infonode.gui.button;
+
+import javax.swing.*;
+import java.io.Serializable;
+
+/**
+ * A button factory.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public interface ButtonFactory extends Serializable {
+  /**
+   * Creates a button.
+   *
+   * @param object argument to the button factory
+   * @return the created button
+   */
+  AbstractButton createButton(Object object);
+}
diff --git a/src/net/infonode/gui/button/FlatButtonFactory.java b/src/net/infonode/gui/button/FlatButtonFactory.java
new file mode 100644
index 0000000..83b938b
--- /dev/null
+++ b/src/net/infonode/gui/button/FlatButtonFactory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FlatButtonFactory.java,v 1.5 2005/02/16 11:28:10 jesper Exp $
+package net.infonode.gui.button;
+
+import javax.swing.*;
+import java.io.Serializable;
+
+/**
+ * Creates a flat button with mouse over highlighting.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class FlatButtonFactory implements ButtonFactory, Serializable {
+  private static final long serialVersionUID = 1;
+
+  public AbstractButton createButton(Object object) {
+    return net.infonode.gui.ButtonFactory.createFlatHighlightButton(null, null, 0, null);
+  }
+}
diff --git a/src/net/infonode/gui/button/package.html b/src/net/infonode/gui/button/package.html
new file mode 100644
index 0000000..22d7005
--- /dev/null
+++ b/src/net/infonode/gui/button/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Utility classes for buttons.
+</body>
+</html>
diff --git a/src/net/infonode/gui/colorprovider/AbstractColorProvider.java b/src/net/infonode/gui/colorprovider/AbstractColorProvider.java
new file mode 100644
index 0000000..e42f19e
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/AbstractColorProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractColorProvider.java,v 1.6 2005/02/16 11:28:10 jesper Exp $
+package net.infonode.gui.colorprovider;
+
+import java.awt.*;
+
+/**
+ * Base class for color providers. It returns the color black.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+abstract public class AbstractColorProvider implements ColorProvider {
+  public Color getColor() {
+    return Color.BLACK;
+  }
+
+  public Color getColor(Component component) {
+    return getColor();
+  }
+}
diff --git a/src/net/infonode/gui/colorprovider/BackgroundColorProvider.java b/src/net/infonode/gui/colorprovider/BackgroundColorProvider.java
new file mode 100644
index 0000000..ced1ae2
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/BackgroundColorProvider.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BackgroundColorProvider.java,v 1.10 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.gui.colorprovider;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.io.ObjectStreamException;
+
+import javax.swing.UIManager;
+
+/**
+ * Returns the background color of a component.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ */
+public class BackgroundColorProvider extends AbstractColorProvider {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final BackgroundColorProvider INSTANCE = new BackgroundColorProvider();
+
+  private BackgroundColorProvider() {
+  }
+
+  public Color getColor(Component component) {
+    Color color = component.getBackground();
+    return color == null ? UIManager.getColor("control") : color;
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/gui/colorprovider/BackgroundPainterColorProvider.java b/src/net/infonode/gui/colorprovider/BackgroundPainterColorProvider.java
new file mode 100644
index 0000000..f5c9bdd
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/BackgroundPainterColorProvider.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BackgroundPainterColorProvider.java,v 1.8 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.gui.colorprovider;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.io.ObjectStreamException;
+
+import javax.swing.UIManager;
+
+import net.infonode.gui.ComponentUtil;
+
+/**
+ * Finds the most suitable background color of a component.
+ * If the component has a {@link net.infonode.gui.componentpainter.ComponentPainter}
+ * that paint its background, like for example a shaped panel, the
+ * color is taken from this painter, otherwise the component background color is used.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class BackgroundPainterColorProvider extends AbstractColorProvider {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * The only instance of this class.
+   */
+  public static final BackgroundPainterColorProvider INSTANCE = new BackgroundPainterColorProvider();
+
+  private BackgroundPainterColorProvider() {
+  }
+
+  public Color getColor(Component component) {
+    Color color = ComponentUtil.getBackgroundColor(component);
+    return color == null ? UIManager.getColor("control") : color;
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return INSTANCE;
+  }
+
+}
diff --git a/src/net/infonode/gui/colorprovider/ColorBlender.java b/src/net/infonode/gui/colorprovider/ColorBlender.java
new file mode 100644
index 0000000..f411f88
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/ColorBlender.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ColorBlender.java,v 1.6 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.gui.colorprovider;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import net.infonode.util.ColorUtil;
+
+/**
+ * Blends two colors according to the given blend amount.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class ColorBlender extends AbstractColorProvider {
+  private static final long serialVersionUID = 1;
+
+  private final ColorProvider color1;
+  private final ColorProvider color2;
+  private final float blendAmount;
+
+  /**
+   * Constructor.
+   *
+   * @param color1      provides the first color
+   * @param color2      provides the second color
+   * @param blendAmount the blend amount, range 0 - 1 where 0 means only the first color and 1 means only the second color
+   */
+  public ColorBlender(ColorProvider color1, ColorProvider color2, float blendAmount) {
+    this.color1 = color1;
+    this.color2 = color2;
+    this.blendAmount = blendAmount;
+  }
+
+  public Color getColor(Component component) {
+    return ColorUtil.blend(color1.getColor(component), color2.getColor(component), blendAmount);
+  }
+
+  public Color getColor() {
+    return ColorUtil.blend(color1.getColor(), color2.getColor(), blendAmount);
+  }
+
+}
diff --git a/src/net/infonode/gui/colorprovider/ColorMultiplier.java b/src/net/infonode/gui/colorprovider/ColorMultiplier.java
new file mode 100644
index 0000000..f13b515
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/ColorMultiplier.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ColorMultiplier.java,v 1.6 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.gui.colorprovider;
+
+import java.awt.Color;
+import java.awt.Component;
+
+import net.infonode.util.ColorUtil;
+
+/**
+ * Multiplies the RGB components of a color with the given factor.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class ColorMultiplier extends AbstractColorProvider {
+  private static final long serialVersionUID = 1;
+
+  private final ColorProvider colorProvider;
+  private final double factor;
+
+  /**
+   * Constructor.
+   *
+   * @param colorProvider provides the color which RGB components will be multiplied
+   * @param factor        the multiply factor
+   */
+  public ColorMultiplier(ColorProvider colorProvider, double factor) {
+    this.colorProvider = colorProvider;
+    this.factor = factor;
+  }
+
+  public Color getColor(Component component) {
+    return ColorUtil.mult(colorProvider.getColor(component), factor);
+  }
+
+  public Color getColor() {
+    return ColorUtil.mult(colorProvider.getColor(), factor);
+  }
+}
diff --git a/src/net/infonode/gui/colorprovider/ColorProvider.java b/src/net/infonode/gui/colorprovider/ColorProvider.java
new file mode 100644
index 0000000..00deb94
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/ColorProvider.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ColorProvider.java,v 1.6 2005/02/16 11:28:10 jesper Exp $
+package net.infonode.gui.colorprovider;
+
+import java.awt.*;
+import java.io.Serializable;
+
+/**
+ * An object that provides colors. Instances of this class is typically used by {@link javax.swing.border.Border}
+ * implementations to obtain border colors which might change depending on the current Look and Feel or which component
+ * the border is painted on.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public interface ColorProvider extends Serializable {
+  /**
+   * Returns the default color when no component is available.
+   *
+   * @return the default color when no component is available
+   */
+  Color getColor();
+
+  /**
+   * Returns the color obtained from the given component.
+   *
+   * @param component the component
+   * @return the color obtained from the given component
+   */
+  Color getColor(Component component);
+}
diff --git a/src/net/infonode/gui/colorprovider/ColorProviderList.java b/src/net/infonode/gui/colorprovider/ColorProviderList.java
new file mode 100644
index 0000000..e49697e
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/ColorProviderList.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ColorProviderList.java,v 1.4 2005/02/16 11:28:10 jesper Exp $
+package net.infonode.gui.colorprovider;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class ColorProviderList extends AbstractColorProvider {
+  private ColorProvider[] providers;
+
+  public ColorProviderList(ColorProvider colorProvider1, ColorProvider colorProvider2) {
+    this(new ColorProvider[]{colorProvider1, colorProvider2});
+  }
+
+  public ColorProviderList(ColorProvider colorProvider1, ColorProvider colorProvider2, ColorProvider colorProvider3) {
+    this(new ColorProvider[]{colorProvider1, colorProvider2, colorProvider3});
+  }
+
+  public ColorProviderList(ColorProvider[] providers) {
+    this.providers = (ColorProvider[]) providers.clone();
+  }
+
+  public Color getColor(Component component) {
+    Color c = null;
+
+    for (int i = 0; i < providers.length; i++) {
+      c = providers[i].getColor(component);
+
+      if (c != null)
+        break;
+    }
+
+    return c;
+  }
+
+  public Color getColor() {
+    Color c = null;
+
+    for (int i = 0; i < providers.length; i++) {
+      c = providers[i].getColor();
+
+      if (c != null)
+        break;
+    }
+
+    return c;
+  }
+}
diff --git a/src/net/infonode/gui/colorprovider/ColorProviderUtil.java b/src/net/infonode/gui/colorprovider/ColorProviderUtil.java
new file mode 100644
index 0000000..9e99af8
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/ColorProviderUtil.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ColorProviderUtil.java,v 1.5 2005/02/16 11:28:10 jesper Exp $
+package net.infonode.gui.colorprovider;
+
+import java.awt.*;
+
+/**
+ * Utility methods for {@link ColorProvider}'s.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class ColorProviderUtil {
+  private ColorProviderUtil() {
+  }
+
+  /**
+   * Returns a {@link ColorProvider} for the color. If the color is null the default provider is returned.
+   *
+   * @param color           the color for which to return a provider
+   * @param defaultProvider the default provider
+   * @return a color provider for the color, if the color is null the default provider is returned
+   */
+  public static ColorProvider getColorProvider(Color color, ColorProvider defaultProvider) {
+    return color == null ? (ColorProvider) defaultProvider : new FixedColorProvider(color);
+  }
+
+}
diff --git a/src/net/infonode/gui/colorprovider/FixedColorProvider.java b/src/net/infonode/gui/colorprovider/FixedColorProvider.java
new file mode 100644
index 0000000..f37b15e
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/FixedColorProvider.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FixedColorProvider.java,v 1.8 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.gui.colorprovider;
+
+import java.awt.Color;
+import java.io.ObjectStreamException;
+
+/**
+ * A {@link ColorProvider} which always returns the same color.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class FixedColorProvider extends AbstractColorProvider {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * A provider for the color black.
+   */
+  public static final FixedColorProvider BLACK = new FixedColorProvider(Color.BLACK);
+
+  /**
+   * A provider for the color white.
+   */
+  public static final FixedColorProvider WHITE = new FixedColorProvider(Color.WHITE);
+
+  private final Color color;
+
+  /**
+   * Constructor.
+   *
+   * @param color the color which this provider will return
+   */
+  public FixedColorProvider(Color color) {
+    this.color = color;
+  }
+
+  public Color getColor() {
+    return color;
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    return color.equals(Color.BLACK) ? BLACK : this;
+  }
+
+}
diff --git a/src/net/infonode/gui/colorprovider/UIManagerColorProvider.java b/src/net/infonode/gui/colorprovider/UIManagerColorProvider.java
new file mode 100644
index 0000000..51921cf
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/UIManagerColorProvider.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: UIManagerColorProvider.java,v 1.11 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.gui.colorprovider;
+
+import java.awt.Color;
+
+import javax.swing.UIManager;
+
+/**
+ * A {@link ColorProvider} which returns a property color from the {@link UIManager}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.11 $
+ */
+public class UIManagerColorProvider extends AbstractColorProvider {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * A provider for the 'control' color.
+   */
+  public static final UIManagerColorProvider CONTROL_COLOR = new UIManagerColorProvider("control", Color.LIGHT_GRAY);
+
+  /**
+   * A provider for the 'controlDkShadow' color.
+   */
+  public static final UIManagerColorProvider CONTROL_DARK_SHADOW = new UIManagerColorProvider("controlDkShadow",
+      Color.BLACK);
+
+  /**
+   * A provider for the 'TabbedPane.highlight' color.
+   */
+  public static final UIManagerColorProvider TABBED_PANE_HIGHLIGHT = new UIManagerColorProvider("TabbedPane.highlight",
+      Color.WHITE);
+
+  /**
+   * A provider for the 'TabbedPane.shadow' color.
+   */
+  public static final UIManagerColorProvider TABBED_PANE_SHADOW = new UIManagerColorProvider("TabbedPane.shadow",
+      Color.BLACK);
+
+  /**
+   * A provider for the 'TabbedPane.darkShadow' color.
+   */
+  public static final UIManagerColorProvider TABBED_PANE_DARK_SHADOW = new UIManagerColorProvider(
+      "TabbedPane.darkShadow", Color.BLACK);
+
+  /**
+   * A provider for the 'TabbedPane.background' color.
+   */
+  public static final UIManagerColorProvider TABBED_PANE_BACKGROUND = new UIManagerColorProvider(
+      "TabbedPane.background", Color.LIGHT_GRAY);
+
+  /**
+   * A provider for the 'Desktop.background' color.
+   */
+  public static final UIManagerColorProvider DESKTOP_BACKGROUND = new UIManagerColorProvider("Desktop.background",
+      Color.BLUE);
+
+  private final String propertyName;
+  private Color defaultColor;
+
+  /**
+   * Constructor.
+   *
+   * @param propertyName the name of the property which value will be retrieved from the {@link UIManager}.
+   */
+  public UIManagerColorProvider(String propertyName) {
+    this.propertyName = propertyName;
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param propertyName the name of the property which value will be retrieved from the {@link UIManager}.
+   * @param defaultColor the color to be used if the specified color doesn't exist in the UIManager
+   */
+  public UIManagerColorProvider(String propertyName, Color defaultColor) {
+    this.propertyName = propertyName;
+    this.defaultColor = defaultColor;
+  }
+
+  public Color getColor() {
+    Color color = UIManager.getColor(propertyName);
+
+    return color == null ? defaultColor : color;
+  }
+}
diff --git a/src/net/infonode/gui/colorprovider/package.html b/src/net/infonode/gui/colorprovider/package.html
new file mode 100644
index 0000000..0411b7d
--- /dev/null
+++ b/src/net/infonode/gui/colorprovider/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Package for color providers. A color provider is an object that provides a color given a component.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/gui/componentpainter/AbstractComponentPainter.java b/src/net/infonode/gui/componentpainter/AbstractComponentPainter.java
new file mode 100644
index 0000000..0321873
--- /dev/null
+++ b/src/net/infonode/gui/componentpainter/AbstractComponentPainter.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractComponentPainter.java,v 1.7 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.componentpainter;
+
+import net.infonode.util.Direction;
+import net.infonode.util.ImageUtils;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.io.Serializable;
+
+/**
+ * An abstract base class for {@link ComponentPainter}'s. Default implementations for both paint methods are provided,
+ * but becuase they call each other a sub class must override one or both methods.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ * @since IDW 1.2.0
+ */
+abstract public class AbstractComponentPainter implements ComponentPainter, Serializable {
+  private static final long serialVersionUID = 1;
+
+  protected AbstractComponentPainter() {
+  }
+
+  public void paint(Component component, Graphics g, int x, int y, int width, int height) {
+    paint(component, g, x, y, width, height, Direction.RIGHT, false, false);
+  }
+
+  public void paint(Component component,
+                    Graphics g,
+                    int x,
+                    int y,
+                    int width,
+                    int height,
+                    Direction direction,
+                    boolean horizontalFlip,
+                    boolean verticalFlip) {
+    if (direction != Direction.RIGHT || horizontalFlip || verticalFlip) {
+      Graphics2D g2 = (Graphics2D) g;
+      AffineTransform t = g2.getTransform();
+
+      try {
+        int w = direction.isHorizontal() ? width : height;
+        int h = direction.isHorizontal() ? height : width;
+        AffineTransform nt = ImageUtils.createTransform(direction, horizontalFlip, verticalFlip, w, h);
+        g2.translate(x, y);
+        g2.transform(nt);
+//        Point p = new Point(x, y);
+//        nt.transform(p, p);
+        paint(component, g, 0, 0, w, h);
+      }
+      finally {
+        g2.setTransform(t);
+      }
+    }
+    else {
+      paint(component, g, x, y, width, height);
+    }
+  }
+
+  public boolean isOpaque(Component component) {
+    return true;
+  }
+
+}
diff --git a/src/net/infonode/gui/componentpainter/AbstractComponentPainterWrapper.java b/src/net/infonode/gui/componentpainter/AbstractComponentPainterWrapper.java
new file mode 100644
index 0000000..d26a3cb
--- /dev/null
+++ b/src/net/infonode/gui/componentpainter/AbstractComponentPainterWrapper.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractComponentPainterWrapper.java,v 1.4 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.componentpainter;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+abstract public class AbstractComponentPainterWrapper extends AbstractComponentPainter {
+  private static final long serialVersionUID = 1;
+
+  private ComponentPainter painter;
+
+  protected AbstractComponentPainterWrapper(ComponentPainter painter) {
+    this.painter = painter;
+  }
+
+  public ComponentPainter getPainter() {
+    return painter;
+  }
+
+  public Color getColor(Component component) {
+    return painter.getColor(component);
+  }
+
+  public boolean isOpaque(Component component) {
+    return painter.isOpaque(component);
+  }
+
+  public void paint(Component component,
+                    Graphics g,
+                    int x,
+                    int y,
+                    int width,
+                    int height,
+                    Direction direction,
+                    boolean horizontalFlip,
+                    boolean verticalFlip) {
+    painter.paint(component, g, x, y, width, height, direction, horizontalFlip, verticalFlip);
+  }
+}
diff --git a/src/net/infonode/gui/componentpainter/ComponentPainter.java b/src/net/infonode/gui/componentpainter/ComponentPainter.java
new file mode 100644
index 0000000..bca5f23
--- /dev/null
+++ b/src/net/infonode/gui/componentpainter/ComponentPainter.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ComponentPainter.java,v 1.9 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.componentpainter;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * <p>
+ * Paints an area of a component.
+ * </p>
+ * <p>
+ * Note: New methods might be added to this interface in the future. To ensure future compatibility inherit from
+ * {@link AbstractComponentPainter} instead of directly implementing this interface.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ * @since IDW 1.2.0
+ */
+public interface ComponentPainter {
+  /**
+   * Paints an area of a component. The area should be painted the same way as for direction Direction.RIGHT without
+   * any flipping.
+   *
+   * @param component the component to paint on
+   * @param g         the graphics to paint on
+   * @param x         the x-coordinate
+   * @param y         the y-coordinate
+   * @param width     the width
+   * @param height    the height
+   */
+  void paint(Component component, Graphics g, int x, int y, int width, int height);
+
+  /**
+   * Paints an area in a specific direction and optinally flipped horizontally and/or vertically. The flips are performed
+   * before the rotation is applied.
+   *
+   * @param component      the component to paint on
+   * @param g              the graphics to paint on
+   * @param x              the x-coordinate
+   * @param y              the y-coordinate
+   * @param width          the width
+   * @param height         the height
+   * @param direction      the direction, Direction.RIGHT is the normal direction
+   * @param horizontalFlip flip the painted graphics horizontally
+   * @param verticalFlip   flip the painted graphics vertically
+   */
+  void paint(Component component, Graphics g, int x, int y, int width, int height, Direction direction,
+             boolean horizontalFlip, boolean verticalFlip);
+
+  /**
+   * Returns true if this painter paints the entire area with an opaque color.
+   *
+   * @param component the component to paint on
+   * @return true if this painter paints the entire area with an opaque color
+   */
+  boolean isOpaque(Component component);
+
+  /**
+   * Returns an approximate average color of the pixels painted by this painter.
+   *
+   * @param component the component to paint on
+   * @return an approximate average color of the pixels painted by this painter
+   */
+  Color getColor(Component component);
+
+}
diff --git a/src/net/infonode/gui/componentpainter/CompoundComponentPainter.java b/src/net/infonode/gui/componentpainter/CompoundComponentPainter.java
new file mode 100644
index 0000000..3036718
--- /dev/null
+++ b/src/net/infonode/gui/componentpainter/CompoundComponentPainter.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: CompoundComponentPainter.java,v 1.8 2005/09/05 14:42:47 johan Exp $
+package net.infonode.gui.componentpainter;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * Paints the same area with two painters.
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.8 $
+ */
+public class CompoundComponentPainter extends AbstractComponentPainter {
+  private static final long serialVersionUID = 1;
+
+  private ComponentPainter bottomPainter;
+  private ComponentPainter topPainter;
+
+  public CompoundComponentPainter(ComponentPainter bottomPainter, ComponentPainter topPainter) {
+    this.bottomPainter = bottomPainter;
+    this.topPainter = topPainter;
+  }
+
+  public void paint(Component component,
+                    Graphics g,
+                    int x,
+                    int y,
+                    int width,
+                    int height,
+                    Direction direction,
+                    boolean horizontalFlip,
+                    boolean verticalFlip) {
+    bottomPainter.paint(component, g, x, y, width, height, direction, horizontalFlip, verticalFlip);
+    topPainter.paint(component, g, x, y, width, height, direction, horizontalFlip, verticalFlip);
+  }
+
+  public boolean isOpaque(Component component) {
+    return bottomPainter.isOpaque(component) || topPainter.isOpaque(component);
+  }
+
+  public Color getColor(Component component) {
+    return topPainter.getColor(component);
+  }
+
+}
diff --git a/src/net/infonode/gui/componentpainter/FixedTransformComponentPainter.java b/src/net/infonode/gui/componentpainter/FixedTransformComponentPainter.java
new file mode 100644
index 0000000..fe1a110
--- /dev/null
+++ b/src/net/infonode/gui/componentpainter/FixedTransformComponentPainter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FixedTransformComponentPainter.java,v 1.6 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.componentpainter;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * A painter that paints its wrapped painter using the same fixed values for direction, horizontal flip and
+ * vertical flip.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class FixedTransformComponentPainter extends AbstractComponentPainterWrapper {
+  private static final long serialVersionUID = 1;
+
+  private Direction direction;
+  private boolean horizontalFlip;
+  private boolean verticalFlip;
+
+  public FixedTransformComponentPainter(ComponentPainter painter) {
+    this(painter, Direction.RIGHT);
+  }
+
+  public FixedTransformComponentPainter(ComponentPainter painter, Direction direction) {
+    this(painter, direction, false, false);
+  }
+
+  public FixedTransformComponentPainter(ComponentPainter painter,
+                                        Direction direction,
+                                        boolean horizontalFlip,
+                                        boolean verticalFlip) {
+    super(painter);
+    this.direction = direction;
+    this.horizontalFlip = horizontalFlip;
+    this.verticalFlip = verticalFlip;
+  }
+
+  public void paint(Component component, Graphics g, int x, int y, int width, int height) {
+    super.paint(component, g, x, y, width, height, direction, horizontalFlip, verticalFlip);
+  }
+
+  public void paint(Component component,
+                    Graphics g,
+                    int x,
+                    int y,
+                    int width,
+                    int height,
+                    Direction direction,
+                    boolean horizontalFlip,
+                    boolean verticalFlip) {
+    paint(component, g, x, y, width, height);
+  }
+
+}
diff --git a/src/net/infonode/gui/componentpainter/GradientComponentPainter.java b/src/net/infonode/gui/componentpainter/GradientComponentPainter.java
new file mode 100644
index 0000000..83b0b0a
--- /dev/null
+++ b/src/net/infonode/gui/componentpainter/GradientComponentPainter.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: GradientComponentPainter.java,v 1.12 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.gui.componentpainter;
+
+import java.awt.*;
+import java.awt.image.MemoryImageSource;
+import java.lang.ref.SoftReference;
+
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.FixedColorProvider;
+import net.infonode.util.ColorUtil;
+import net.infonode.util.Direction;
+import net.infonode.util.ImageUtils;
+
+/**
+ * A painter that paints an gradient area specified by four corner colors.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.12 $
+ */
+public class GradientComponentPainter extends AbstractComponentPainter {
+  private static final long serialVersionUID = 1;
+
+  private final ColorProvider[] colorProviders = new ColorProvider[4];
+  private transient Color[] colors;
+  private final int size = 128;
+  private transient SoftReference[] images;
+  private transient boolean hasAlpha;
+
+  /**
+   * Constructor.
+   *
+   * @param topLeftColor     the top left corner color
+   * @param topRightColor    the top right corner color
+   * @param bottomLeftColor  the bottom left corner color
+   * @param bottomRightColor the bottom right corner color
+   */
+  public GradientComponentPainter(Color topLeftColor, Color topRightColor, Color bottomLeftColor,
+                                  Color bottomRightColor) {
+    this(new FixedColorProvider(topLeftColor), new FixedColorProvider(topRightColor), new FixedColorProvider(
+        bottomLeftColor),
+        new FixedColorProvider(bottomRightColor));
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param topLeftColor     the top left corner color provider
+   * @param topRightColor    the top right corner color provider
+   * @param bottomLeftColor  the bottom left corner color provider
+   * @param bottomRightColor the bottom right corner color provider
+   */
+  public GradientComponentPainter(ColorProvider topLeftColor, ColorProvider topRightColor, ColorProvider bottomLeftColor,
+                                  ColorProvider bottomRightColor) {
+    colorProviders[0] = topLeftColor;
+    colorProviders[1] = topRightColor;
+    colorProviders[2] = bottomLeftColor;
+    colorProviders[3] = bottomRightColor;
+  }
+
+  public void paint(Component component,
+                    Graphics g,
+                    int x,
+                    int y,
+                    int width,
+                    int height,
+                    Direction direction,
+                    boolean horizontalFlip,
+                    boolean verticalFlip) {
+    updateColors(component);
+
+    if (colors[0] != null && colors[1] != null && colors[2] != null && colors[3] != null) {
+      if (colors[0].equals(colors[2]) && colors[1].equals(colors[3]) && colors[0].equals(colors[1])) {
+        g.setColor(colors[0]);
+        g.fillRect(x, y, width, height);
+      }
+      else {
+        int imageIndex = direction.getValue() + (horizontalFlip ? 4 : 0) + (verticalFlip ? 8 : 0);
+        SoftReference ref = images[imageIndex];
+        Image image = ref == null ? null : (Image) ref.get();
+
+        if (image == null) {
+          image = createGradientImage(fixColors(direction, horizontalFlip, verticalFlip));
+          images[imageIndex] = new SoftReference(image);
+        }
+
+        g.drawImage(image, x, y, width, height, null);
+      }
+    }
+
+  }
+
+  /*
+   * private Image createLineImage(Direction direction, Color c1, Color c2) {
+   * BufferedImage image = new BufferedImage(direction.isHorizontal() ? size : 1,
+   * direction.isHorizontal() ? 1 : size, BufferedImage.TYPE_INT_RGB);
+   * 
+   * int dx = direction == Direction.RIGHT ? 1 : direction == Direction.LEFT ? -1 :
+   * 0; int dy = direction == Direction.DOWN ? 1 : direction == Direction.UP ? -1 :
+   * 0;
+   * 
+   * int x = direction == Direction.LEFT ? size - 1 : 0; int y = direction ==
+   * Direction.UP ? size - 1 : 0;
+   * 
+   * for (int i = 0; i < size; i++) { Color c = ColorUtil.blend(c1, c2, (double) i /
+   * size); image.setRGB(x, y, c.getRGB()); x += dx; y += dy; }
+   * 
+   * return image; }
+   */
+  /*  private static void drawLines(Direction direction, Graphics g, int x, int y, int width, int height, Color c1, Color c2) {
+    int size = direction.isHorizontal() ? width : height;
+    Int4 c = ImageUtils.toInt4(c1);
+    Int4 dc = ImageUtils.toInt4(c2).sub(ImageUtils.toInt4(c1)).div(size);
+
+    for (int i = 0; i < size; i++) {
+      g.setColor(ImageUtils.toColor(c));
+
+      if (direction == Direction.RIGHT)
+        g.drawLine(x + i, y, x + i, y + height - 1);
+      else if (direction == Direction.DOWN)
+        g.drawLine(x, y + i, x + width - 1, y + i);
+      else if (direction == Direction.LEFT)
+        g.drawLine(x + width - 1 - i, y, x + width - 1 - i, y + height - 1);
+      else
+        g.drawLine(x, y + height - 1 - i, x + width - 1, y + height - 1 - i);
+
+      c.add(dc);
+    }
+  }*/
+
+  private Color[] fixColors(Direction direction, boolean horizontalFlip, boolean verticalFlip) {
+    Color[] c = new Color[4];
+    Color t;
+
+    if (horizontalFlip) {
+      c[0] = colors[1];
+      c[1] = colors[0];
+      c[2] = colors[3];
+      c[3] = colors[2];
+    }
+    else {
+      c[0] = colors[0];
+      c[1] = colors[1];
+      c[2] = colors[2];
+      c[3] = colors[3];
+    }
+
+    if (verticalFlip) {
+      t = c[2];
+      c[2] = c[0];
+      c[0] = t;
+
+      t = c[3];
+      c[3] = c[1];
+      c[1] = t;
+    }
+
+    if (direction == Direction.RIGHT) {
+      return c;
+    }
+    else if (direction == Direction.DOWN) {
+      t = c[0];
+      c[0] = c[2];
+      c[2] = c[3];
+      c[3] = c[1];
+      c[1] = t;
+    }
+    else if (direction == Direction.LEFT) {
+      t = c[0];
+      c[0] = c[3];
+      c[3] = t;
+
+      t = c[1];
+      c[1] = c[2];
+      c[2] = t;
+    }
+    else if (direction == Direction.UP) {
+      t = c[0];
+      c[0] = c[1];
+      c[1] = c[3];
+      c[3] = c[2];
+      c[2] = t;
+    }
+
+    return c;
+  }
+
+  private Image createGradientImage(Color[] colors) {
+    return Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(size,
+        size,
+        ImageUtils.createGradientPixels(colors,
+            size,
+            size),
+            0,
+            size));
+  }
+
+  private void updateColors(Component component) {
+    if (images == null) {
+      images = new SoftReference[16];
+    }
+
+    if (colors == null)
+      colors = new Color[4];
+
+    for (int i = 0; i < colors.length; i++) {
+      Color c = colorProviders[i].getColor(component);
+
+      if (c != null && !c.equals(colors[i])) {
+        for (int j = 0; j < images.length; j++) {
+          images[j] = null;
+        }
+      }
+
+      colors[i] = c;
+      hasAlpha |= c != null && c.getAlpha() != 255;
+    }
+  }
+
+  public boolean isOpaque(Component component) {
+    updateColors(component);
+    return !hasAlpha;
+  }
+
+  public Color getColor(Component component) {
+    updateColors(component);
+    return ColorUtil.blend(ColorUtil.blend(colors[0], colors[1], 0.5),
+        ColorUtil.blend(colors[2], colors[3], 0.5),
+        0.5);
+  }
+}
diff --git a/src/net/infonode/gui/componentpainter/RectangleComponentPainter.java b/src/net/infonode/gui/componentpainter/RectangleComponentPainter.java
new file mode 100644
index 0000000..73d5cc9
--- /dev/null
+++ b/src/net/infonode/gui/componentpainter/RectangleComponentPainter.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RectangleComponentPainter.java,v 1.8 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.gui.componentpainter;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Insets;
+
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.FixedColorProvider;
+import net.infonode.util.Direction;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class RectangleComponentPainter extends AbstractComponentPainter {
+  private static final long serialVersionUID = 1;
+
+  private final ColorProvider color;
+  private final ColorProvider xorColor;
+  private final Insets insets;
+
+  public RectangleComponentPainter(Color color, int lineWidth) {
+    this(new FixedColorProvider(color), lineWidth);
+  }
+
+  public RectangleComponentPainter(Color color, Color xorColor, int lineWidth) {
+    this(new FixedColorProvider(color), new FixedColorProvider(xorColor), lineWidth);
+  }
+
+  public RectangleComponentPainter(ColorProvider color, int lineWidth) {
+    this(color, null, lineWidth);
+  }
+
+  public RectangleComponentPainter(ColorProvider color, ColorProvider xorColor, int lineWidth) {
+    this(color, xorColor, new Insets(lineWidth, lineWidth, lineWidth, lineWidth));
+  }
+
+  public RectangleComponentPainter(ColorProvider color, ColorProvider xorColor, Insets insets) {
+    this.color = color;
+    this.xorColor = xorColor;
+    this.insets = (Insets) insets.clone();
+  }
+
+  public void paint(Component component,
+                    Graphics g,
+                    int x,
+                    int y,
+                    int width,
+                    int height,
+                    Direction direction,
+                    boolean horizontalFlip,
+                    boolean verticalFlip) {
+    Color xc = null;
+    g.setColor(color.getColor(component));
+
+    if (xorColor != null) {
+      xc = xorColor.getColor(component);
+
+      if (xc != null)
+        g.setXORMode(xc);
+    }
+
+    Insets i = InsetsUtil.rotate(direction, new Insets(verticalFlip ? insets.bottom : insets.top,
+                                                                    horizontalFlip ? insets.right : insets.left,
+                                                                                   verticalFlip ? insets.top : insets.bottom,
+                                                                                                horizontalFlip ? insets.left : insets.right));
+
+    g.fillRect(x + i.left, y, width - i.left - i.right, i.top);
+    g.fillRect(x + i.left, y + height - i.bottom, width - i.left - i.right, i.bottom);
+    g.fillRect(x, y, i.left, height);
+    g.fillRect(x + width - i.right, y, i.right, height);
+
+    if (xc != null)
+      g.setPaintMode();
+  }
+
+  public boolean isOpaque(Component component) {
+    return false;
+  }
+
+  public Color getColor(Component component) {
+    return color.getColor(component);
+  }
+}
diff --git a/src/net/infonode/gui/componentpainter/SolidColorComponentPainter.java b/src/net/infonode/gui/componentpainter/SolidColorComponentPainter.java
new file mode 100644
index 0000000..168142b
--- /dev/null
+++ b/src/net/infonode/gui/componentpainter/SolidColorComponentPainter.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SolidColorComponentPainter.java,v 1.9 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.componentpainter;
+
+import net.infonode.gui.colorprovider.BackgroundColorProvider;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * Paints an area with a solid color.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ */
+public class SolidColorComponentPainter extends AbstractComponentPainter {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * Paints a component using the background color set in the component.
+   */
+  public static final SolidColorComponentPainter BACKGROUND_COLOR_PAINTER =
+      new SolidColorComponentPainter(BackgroundColorProvider.INSTANCE);
+
+  private ColorProvider colorProvider;
+
+  /**
+   * Constructor.
+   *
+   * @param colorProvider the color provider
+   */
+  public SolidColorComponentPainter(ColorProvider colorProvider) {
+    this.colorProvider = colorProvider;
+  }
+
+  public void paint(Component component,
+                    Graphics g,
+                    int x,
+                    int y,
+                    int width,
+                    int height,
+                    Direction direction,
+                    boolean horizontalFlip,
+                    boolean verticalFlip) {
+    g.setColor(colorProvider.getColor(component));
+    g.fillRect(x, y, width, height);
+  }
+
+  public boolean isOpaque(Component component) {
+    return colorProvider.getColor(component).getAlpha() == 255;
+  }
+
+  public Color getColor(Component component) {
+    return colorProvider.getColor(component);
+  }
+}
diff --git a/src/net/infonode/gui/componentpainter/package.html b/src/net/infonode/gui/componentpainter/package.html
new file mode 100644
index 0000000..5f41fe6
--- /dev/null
+++ b/src/net/infonode/gui/componentpainter/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Package for component painters. A component painter paints an area of a component.  
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/gui/draggable/DraggableComponent.java b/src/net/infonode/gui/draggable/DraggableComponent.java
new file mode 100644
index 0000000..cbfadd1
--- /dev/null
+++ b/src/net/infonode/gui/draggable/DraggableComponent.java
@@ -0,0 +1,557 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DraggableComponent.java,v 1.32 2009/02/05 15:57:56 jesper Exp $
+
+package net.infonode.gui.draggable;
+
+import java.awt.*;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+import javax.swing.event.MouseInputAdapter;
+import javax.swing.event.MouseInputListener;
+
+import net.infonode.gui.ComponentUtil;
+import net.infonode.gui.EventUtil;
+
+public class DraggableComponent {
+  private final JComponent component;
+  private JComponent[] eventComponents;
+
+  private boolean reorderEnabled = true;
+  private boolean enabled = true;
+  private boolean reorderRestoreOnDrag;
+  private boolean detectOuterAreaAsLine = true;
+  private boolean enableInsideDrag;
+  private boolean selectOnMousePress;
+
+  private boolean mousePressed;
+  private boolean dragEventFired;
+  private boolean dragStarted;
+
+  private boolean ignoreAddNotify = false;
+
+  private int dragIndex;
+  private int dragFromIndex;
+  private int abortDragKeyCode = KeyEvent.VK_ESCAPE;
+
+  private ArrayList layoutOrderList;
+
+  private ArrayList listeners;
+  private JComponent outerParentArea;
+
+  private final KeyEventDispatcher abortDragKeyDispatcher = new KeyEventDispatcher() {
+    public boolean dispatchKeyEvent(KeyEvent e) {
+      if (mousePressed && e.getKeyCode() == abortDragKeyCode) {
+        if (e.getID() == KeyEvent.KEY_PRESSED)
+          dragCompleted(null);
+        return true;
+      }
+      return false;
+    }
+  };
+
+  private final MouseInputListener mouseInputListener = new MouseInputAdapter() {
+    public void mousePressed(MouseEvent e) {
+      //if (MouseEventCoalesceManager.getInstance().isPressedAllowed(e))
+      pressed(e);
+    }
+
+    public void mouseReleased(MouseEvent e) {
+      //if (MouseEventCoalesceManager.getInstance().isReleasedAllowed(e))
+      released(e);
+    }
+
+    public void mouseDragged(MouseEvent e) {
+      //if (MouseEventCoalesceManager.getInstance().isDraggedAllowed(e))
+      dragged(e);
+    }
+  };
+
+  public DraggableComponent(JComponent component) {
+    this(component, component);
+  }
+
+  public DraggableComponent(JComponent component, JComponent eventComponent) {
+    this(component, new JComponent[]{eventComponent});
+  }
+
+  public DraggableComponent(JComponent component, JComponent[] eventComponents) {
+    this.component = component;
+    component.addComponentListener(new ComponentAdapter() {
+
+      public void componentResized(ComponentEvent e) {
+        fireChangedEvent(DraggableComponentEvent.TYPE_UNDEFINED);
+      }
+
+      /*public void componentMoved(ComponentEvent e) {
+        fireChangedEvent(DraggableComponentEvent.TYPE_MOVED);
+      }*/
+    });
+    setEventComponents(eventComponents);
+  }
+
+  public void addListener(DraggableComponentListener l) {
+    if (listeners == null)
+      listeners = new ArrayList(2);
+
+    listeners.add(l);
+  }
+
+  public void removeListener(DraggableComponentListener l) {
+    if (listeners != null) {
+      listeners.remove(l);
+
+      if (listeners.size() == 0)
+        listeners = null;
+    }
+  }
+
+  public JComponent getComponent() {
+    return component;
+  }
+
+  public JComponent[] getEventComponents() {
+    return eventComponents;
+  }
+
+  public void setEventComponents(JComponent[] eventComponents) {
+    if (this.eventComponents != null) {
+      for (int i = 0; i < this.eventComponents.length; i++) {
+        this.eventComponents[i].removeMouseListener(mouseInputListener);
+        this.eventComponents[i].removeMouseMotionListener(mouseInputListener);
+      }
+    }
+
+    this.eventComponents = eventComponents;
+
+    if (this.eventComponents != null) {
+      for (int i = 0; i < this.eventComponents.length; i++) {
+        this.eventComponents[i].addMouseListener(mouseInputListener);
+        this.eventComponents[i].addMouseMotionListener(mouseInputListener);
+      }
+    }
+  }
+
+  public int getAbortDragKeyCode() {
+    return abortDragKeyCode;
+  }
+
+  public void setAbortDragKeyCode(int abortDragKeyCode) {
+    this.abortDragKeyCode = abortDragKeyCode;
+  }
+
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  public void setEnabled(boolean enabled) {
+    if (this.enabled != enabled) {
+      this.enabled = enabled;
+      fireChangedEvent(enabled ? DraggableComponentEvent.TYPE_ENABLED : DraggableComponentEvent.TYPE_DISABLED);
+    }
+  }
+
+  public boolean isReorderEnabled() {
+    return reorderEnabled;
+  }
+
+  public void setReorderEnabled(boolean reorderEnabled) {
+    this.reorderEnabled = reorderEnabled;
+  }
+
+  public boolean isReorderRestoreOnDrag() {
+    return reorderRestoreOnDrag;
+  }
+
+  public void setReorderRestoreOnDrag(boolean reorderRestoreOnDrag) {
+    this.reorderRestoreOnDrag = reorderRestoreOnDrag;
+  }
+
+  public boolean isDetectOuterAreaAsLine() {
+    return detectOuterAreaAsLine;
+  }
+
+  public void setDetectOuterAreaAsLine(boolean detectOuterAreaAsLine) {
+    this.detectOuterAreaAsLine = detectOuterAreaAsLine;
+  }
+
+  public boolean isEnableInsideDrag() {
+    return enableInsideDrag;
+  }
+
+  public void setEnableInsideDrag(boolean enableInsideDrag) {
+    this.enableInsideDrag = enableInsideDrag;
+  }
+
+  public boolean isSelectOnMousePress() {
+    return selectOnMousePress;
+  }
+
+  public void setSelectOnMousePress(boolean selectOnMousePress) {
+    this.selectOnMousePress = selectOnMousePress;
+  }
+
+  public void drag(Point p) {
+    if (enabled) {
+      dragIndex = getComponentIndex(component);
+      dragFromIndex = dragIndex;
+      doDrag(p);
+    }
+  }
+
+  public void abortDrag() {
+    if (dragStarted)
+      dragCompleted(null);
+  }
+
+  public void setLayoutOrderList(ArrayList layoutOrderList) {
+    this.layoutOrderList = layoutOrderList;
+  }
+
+  public void select() {
+    if (enabled)
+      fireSelectedEvent();
+  }
+
+  public void setOuterParentArea(JComponent outerParentArea) {
+    this.outerParentArea = outerParentArea;
+  }
+
+  public boolean isIgnoreAddNotify() {
+    return ignoreAddNotify;
+  }
+
+  public void setIgnoreAddNotify(boolean ignoreAddNotify) {
+    this.ignoreAddNotify = ignoreAddNotify;
+  }
+
+  private void pressed(MouseEvent e) {
+    if (enabled && e.getButton() == MouseEvent.BUTTON1) {
+      if (selectOnMousePress && !e.isShiftDown())
+        select();
+      dragStarted = false;
+      KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(abortDragKeyDispatcher);
+      mousePressed = true;
+      dragIndex = getComponentIndex(component);
+      dragFromIndex = dragIndex;
+
+      fireChangedEvent(DraggableComponentEvent.TYPE_PRESSED);
+    }
+  }
+
+  private void released(MouseEvent e) {
+    if (mousePressed) {
+      if (e.getButton() == MouseEvent.BUTTON1)
+        dragCompleted(e);
+      else {
+        dragCompleted(null);
+        e.consume();
+      }
+    }
+  }
+
+  private void dragged(MouseEvent e) {
+    if (enabled && mousePressed) {
+      Point p = SwingUtilities.convertPoint((JComponent) e.getSource(), e.getPoint(), component);
+      if (dragStarted || enableInsideDrag || !component.contains(p)) {
+        if (reorderEnabled)
+          doDrag(p);
+        else
+          dragStarted = true;
+
+        fireDraggedEvent(EventUtil.convert(e, component, p));
+      }
+    }
+  }
+
+  private void dragCompleted(MouseEvent e) {
+    mousePressed = false;
+    dragStarted = false;
+
+    KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(abortDragKeyDispatcher);
+
+    if (e == null) {
+      restoreComponentOrder();
+      fireNotDroppedEvent();
+    }
+    else if (!checkParentContains(
+        SwingUtilities.convertPoint((JComponent) e.getSource(), e.getPoint(), component.getParent()))) {
+      restoreComponentOrder();
+      fireDroppedEvent(EventUtil.convert(e, component));
+    }
+    else {
+      fireDroppedEvent(EventUtil.convert(e, component));
+      //if (component.contains(p))
+      if (!selectOnMousePress && !e.isShiftDown())
+        fireSelectedEvent();
+    }
+
+    fireChangedEvent(DraggableComponentEvent.TYPE_RELEASED);
+  }
+
+  private void updateParent() {
+    if (component.getParent() != null) {
+      ComponentUtil.validate(component.getParent());
+      //component.getParent().repaint();
+    }
+  }
+
+  private void doDrag(Point p) {
+    dragStarted = true;
+    JComponent parent = (JComponent) component.getParent();
+
+    if (parent.getComponentCount() == 1)
+      return;
+
+    Point p2 = SwingUtilities.convertPoint(component, p, parent);
+    int toIndex = getMoveComponentIndex(p2);
+    if (toIndex != -1) {
+      toIndex = Math.min(toIndex, parent.getComponentCount() - 1);
+      Component fromComponent = getComponent(parent, dragIndex);
+      int fromDimension;
+      int toPos;
+      int toDimension;
+
+      if (isVerticalDrag()) {
+        fromDimension = fromComponent.getHeight();
+        toPos = (int) SwingUtilities.convertPoint(parent, p2, getComponent(parent, toIndex)).getY();
+        toDimension = getComponent(parent, toIndex).getHeight();
+      }
+      else {
+        fromDimension = fromComponent.getWidth();
+        toPos = (int) SwingUtilities.convertPoint(parent, p2, getComponent(parent, toIndex)).getX();
+        toDimension = getComponent(parent, toIndex).getWidth();
+      }
+
+      if ((toIndex > dragIndex && toDimension - toPos > fromDimension) ||
+          ((dragIndex == -1 || toIndex < dragIndex) && toPos > fromDimension))
+        return;
+
+      if (dragIndex != -1 && dragIndex != toIndex) {
+        removeComponent(parent, fromComponent, dragIndex);
+        addComponent(parent, fromComponent, toIndex);
+        fireChangedEvent(DraggableComponentEvent.TYPE_MOVED);
+      }
+    }
+
+    if (toIndex < 0) {
+      restoreComponentOrder();
+    }
+    else
+      dragIndex = toIndex;
+  }
+
+  private boolean isVerticalDrag() {
+    JComponent parent = (JComponent) component.getParent();
+    if (parent.getComponentCount() > 1)
+      return getComponent(parent, 0).getY() < getComponent(parent, 1).getY();
+
+    return false;
+  }
+
+  private boolean checkParentContains(Point p) {
+    if (outerParentArea == null)
+      return component.getParent().contains(p);
+
+    Point p2 = SwingUtilities.convertPoint(component.getParent(), p, outerParentArea);
+    if (detectOuterAreaAsLine) {
+      Insets i = new Insets(0, 0, 0, 0);//outerParentArea.getInsets();
+      return component.getParent().contains(p)
+      ||
+      (outerParentArea.contains(p2) &&
+          (isVerticalDrag()
+              ?
+               (p2.getX() >= i.left && p2.getX() < (outerParentArea.getWidth() - i.right))
+               :
+                 (p2.getY() >= i.top && p2.getY() < (outerParentArea.getHeight() - i.bottom))));
+    }
+
+    return component.getParent().contains(p) || outerParentArea.contains(p2);
+  }
+
+  private int getMoveComponentIndex(Point p) {
+    JComponent parent = (JComponent) component.getParent();
+    if (checkParentContains(p)) {
+      boolean vertical = isVerticalDrag();
+      for (int i = 0; i < parent.getComponentCount() - 1; i++) {
+        Point p2 = getComponent(parent, i + 1).getLocation();
+
+        if (vertical) {
+          if (p.getY() >= 0 && p.getY() < p2.getY())
+            return i;
+        }
+        else {
+          if (p.getX() >= 0 && p.getX() < p2.getX())
+            return i;
+        }
+      }
+
+      if (dragIndex == -1)
+        return parent.getComponentCount();
+      else if (vertical)
+        return p.getY() < 0 ? 0 : parent.getComponentCount() - 1;
+      else
+        return p.getX() < 0 ? 0 : parent.getComponentCount() - 1;
+    }
+
+    return -1;
+  }
+
+  private JComponent getComponent(Container parent, int index) {
+    if (layoutOrderList != null)
+      return (JComponent) layoutOrderList.get(index);
+
+    return (JComponent) parent.getComponent(index);
+  }
+
+  private int getComponentIndex(Component c) {
+    if (layoutOrderList != null)
+      return layoutOrderList.indexOf(c);
+
+    return ComponentUtil.getComponentIndex(c);
+  }
+
+  private void addComponent(Container parent, Component c, int index) {
+    if (layoutOrderList != null) {
+      layoutOrderList.add(index, c);
+      parent.add(c, index);
+    }
+    else
+      parent.add(c, index);
+
+    revalidateComponentTree((JComponent) c);
+  }
+
+  private void removeComponent(Container parent, Component c, int index) {
+    revalidateComponentTree((JComponent) c);
+
+    if (layoutOrderList != null)
+      if (index < 0) {
+        layoutOrderList.remove(c);
+        parent.remove(c);
+      }
+      else {
+        Component tmp = (Component) layoutOrderList.get(index);
+        layoutOrderList.remove(index);
+        parent.remove(tmp);
+      }
+    else if (index < 0)
+      parent.remove(c);
+    else
+      parent.remove(index);
+  }
+
+  private void revalidateComponentTree(JComponent c) {
+    Container parent = c.getParent();
+    int index = ComponentUtil.getComponentIndex(c);
+    if (index > 0)
+      doRevalidateComponentTree((JComponent) parent.getComponent(index - 1));
+    doRevalidateComponentTree(c);
+    if (index < parent.getComponentCount() - 1)
+      doRevalidateComponentTree((JComponent) parent.getComponent(index + 1));
+  }
+
+  private void doRevalidateComponentTree(JComponent c) {
+    c.revalidate();
+    int count = c.getComponentCount();
+    for (int i = 0; i < count; i++)
+      doRevalidateComponentTree(((JComponent) c.getComponent(i)));
+  }
+
+  private void restoreComponentOrder() {
+    if (reorderEnabled && dragIndex != -1 && dragFromIndex != -1 && dragIndex != dragFromIndex) {
+      Container parent = component.getParent();
+      Component comp = getComponent(parent, dragIndex);
+      removeComponent(parent, comp, -1);
+      dragIndex = dragFromIndex;
+      addComponent(parent, comp, dragIndex);
+      fireChangedEvent(DraggableComponentEvent.TYPE_MOVED);
+    }
+  }
+
+  private void fireChangedEvent(int type) {
+    updateParent();
+
+    if (listeners != null) {
+      DraggableComponentEvent event = new DraggableComponentEvent(this, type);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentListener) l[i]).changed(event);
+    }
+  }
+
+  private void fireSelectedEvent() {
+    updateParent();
+
+    if (listeners != null) {
+      DraggableComponentEvent event = new DraggableComponentEvent(this);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentListener) l[i]).selected(event);
+    }
+  }
+
+  private void fireDraggedEvent(MouseEvent mouseEvent) {
+    dragEventFired = true;
+    if (listeners != null) {
+      DraggableComponentEvent event = new DraggableComponentEvent(this, mouseEvent);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentListener) l[i]).dragged(event);
+    }
+  }
+
+  private void fireDroppedEvent(MouseEvent mouseEvent) {
+    updateParent();
+
+    if (dragEventFired) {
+      dragEventFired = false;
+      if (listeners != null) {
+        DraggableComponentEvent event = new DraggableComponentEvent(this, mouseEvent);
+        Object[] l = listeners.toArray();
+        for (int i = 0; i < l.length; i++)
+          ((DraggableComponentListener) l[i]).dropped(event);
+      }
+    }
+  }
+
+  private void fireNotDroppedEvent() {
+    updateParent();
+
+    if (dragEventFired) {
+      dragEventFired = false;
+      if (listeners != null) {
+        DraggableComponentEvent event = new DraggableComponentEvent(this);
+        Object[] l = listeners.toArray();
+        for (int i = 0; i < l.length; i++)
+          ((DraggableComponentListener) l[i]).dragAborted(event);
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/draggable/DraggableComponentAdapter.java b/src/net/infonode/gui/draggable/DraggableComponentAdapter.java
new file mode 100644
index 0000000..7c3d236
--- /dev/null
+++ b/src/net/infonode/gui/draggable/DraggableComponentAdapter.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DraggableComponentAdapter.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.gui.draggable;
+
+public class DraggableComponentAdapter implements DraggableComponentListener {
+  public void changed(DraggableComponentEvent event) {
+  }
+
+  public void selected(DraggableComponentEvent event) {
+  }
+
+  public void dragged(DraggableComponentEvent event) {
+  }
+
+  public void dropped(DraggableComponentEvent event) {
+  }
+
+  public void dragAborted(DraggableComponentEvent event) {
+  }
+}
diff --git a/src/net/infonode/gui/draggable/DraggableComponentBox.java b/src/net/infonode/gui/draggable/DraggableComponentBox.java
new file mode 100644
index 0000000..2c3ac79
--- /dev/null
+++ b/src/net/infonode/gui/draggable/DraggableComponentBox.java
@@ -0,0 +1,732 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DraggableComponentBox.java,v 1.53 2009/02/05 15:57:56 jesper Exp $
+
+package net.infonode.gui.draggable;
+
+import java.awt.*;
+import java.awt.event.ComponentAdapter;
+import java.awt.event.ComponentEvent;
+import java.util.ArrayList;
+
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+
+import net.infonode.gui.*;
+import net.infonode.gui.layout.DirectionLayout;
+import net.infonode.gui.panel.SimplePanel;
+import net.infonode.util.Direction;
+
+public class DraggableComponentBox extends SimplePanel {
+  private final boolean componentBoxEnabled = true;
+
+  private final JComponent componentBox;
+  private JComponent componentContainer;
+
+  private JComponent outerParentArea = this;
+  private Direction componentDirection = Direction.UP;
+  private boolean scrollEnabled = false;
+  private boolean ensureSelectedVisible;
+  private boolean autoSelect = true;
+  private boolean descendingSortOrder = true;
+
+  private boolean doReverseSort = false;
+  private boolean mustSort = false;
+
+  private int scrollOffset;
+  private final int iconSize;
+  private DraggableComponent selectedComponent;
+  private DraggableComponent topComponent;
+  private ArrayList listeners;
+  private final ArrayList draggableComponentList = new ArrayList(10);
+  private final ArrayList layoutOrderList = new ArrayList(10);
+
+  private ScrollButtonBox scrollButtonBox;
+
+  private boolean useDefaultScrollButtons = true;
+
+  private final DraggableComponentListener draggableComponentListener = new DraggableComponentListener() {
+    public void changed(DraggableComponentEvent event) {
+      if (event.getType() == DraggableComponentEvent.TYPE_MOVED) {
+        sortComponentList(!descendingSortOrder);
+      }
+      fireChangedEvent(event);
+    }
+
+    public void selected(DraggableComponentEvent event) {
+      doSelectComponent(event.getSource());
+    }
+
+    public void dragged(DraggableComponentEvent event) {
+      fireDraggedEvent(event);
+    }
+
+    public void dropped(DraggableComponentEvent event) {
+      ensureSelectedVisible();
+      fireDroppedEvent(event);
+    }
+
+    public void dragAborted(DraggableComponentEvent event) {
+      ensureSelectedVisible();
+      fireNotDroppedEvent(event);
+    }
+  };
+
+  public DraggableComponentBox(int iconSize) {
+    this(iconSize, true);
+  }
+
+  public DraggableComponentBox(int iconSize, boolean useDefaultScrollButtons) {
+    this.iconSize = iconSize;
+    this.useDefaultScrollButtons = useDefaultScrollButtons;
+    // Fix minimum size when flipping direction
+    final DirectionLayout layout = new DirectionLayout(componentDirection == Direction.UP ? Direction.RIGHT : componentDirection == Direction.LEFT
+                                                                                          ?
+                                                                                           Direction.DOWN
+                                                                                           :
+                                                                                             componentDirection == Direction.DOWN ?
+                                                                                                                                   Direction.RIGHT :
+                                                                                                                                     Direction.DOWN) {
+      public Dimension minimumLayoutSize(Container parent) {
+        Dimension min = super.minimumLayoutSize(parent);
+        Dimension pref = super.preferredLayoutSize(parent);
+        return componentDirection.isHorizontal() ?
+                                                  new Dimension(pref.width, min.height) : new Dimension(min.width, pref.height);
+      }
+
+      public void layoutContainer(Container parent) {
+        if (DraggableComponentBox.this != null && componentBoxEnabled) {
+          //long millis = System.currentTimeMillis();
+          doSort();
+          super.layoutContainer(parent);
+          //System.out.println("Layout: " + (System.currentTimeMillis() - millis));
+        }
+      }
+
+      public Dimension preferredLayoutSize(Container parent) {
+        doSort();
+        return super.preferredLayoutSize(parent);
+      }
+    };
+
+    layout.setLayoutOrderList(layoutOrderList);
+
+    componentBox = new SimplePanel(layout) {
+      public boolean isOptimizedDrawingEnabled() {
+        return DraggableComponentBox.this != null && getComponentSpacing() >= 0;
+      }
+    };
+
+    componentBox.addComponentListener(new ComponentAdapter() {
+      public void componentResized(ComponentEvent e) {
+        //fireChangedEvent();
+      }
+
+      public void componentMoved(ComponentEvent e) {
+        fireChangedEvent();
+      }
+    });
+
+    initialize();
+  }
+
+  public void addListener(DraggableComponentBoxListener listener) {
+    if (listeners == null)
+      listeners = new ArrayList(2);
+
+    listeners.add(listener);
+  }
+
+  public void removeListener(DraggableComponentBoxListener listener) {
+    if (listeners != null) {
+      listeners.remove(listener);
+
+      if (listeners.size() == 0)
+        listeners = null;
+    }
+  }
+
+  public void addDraggableComponent(DraggableComponent component) {
+    insertDraggableComponent(component, -1);
+  }
+
+  public void insertDraggableComponent(DraggableComponent component, int index) {
+    component.setLayoutOrderList(layoutOrderList);
+
+    component.addListener(draggableComponentListener);
+    if (index < 0) {
+      layoutOrderList.add(component.getComponent());
+      componentBox.add(component.getComponent());
+    }
+    else {
+      layoutOrderList.add(index, component.getComponent());
+      componentBox.add(component.getComponent(), index);
+    }
+
+    sortComponentList(!descendingSortOrder);
+
+    draggableComponentList.add(component);
+    component.setOuterParentArea(outerParentArea);
+    componentBox.revalidate();
+
+    fireAddedEvent(component);
+    if (autoSelect && layoutOrderList.size() == 1 && selectedComponent == null && component.isEnabled())
+      doSelectComponent(component);
+
+    updateScrollButtons();
+  }
+
+  private void updateScrollButtons() {
+    if (scrollButtonBox != null) {
+      ScrollableBox scrollableBox = (ScrollableBox) componentContainer;
+      scrollButtonBox.setButton1Enabled(!scrollableBox.isLeftEnd());
+      scrollButtonBox.setButton2Enabled(!scrollableBox.isRightEnd());
+    }
+  }
+
+  public void insertDraggableComponent(DraggableComponent component, Point p) {
+    int componentIndex = getComponentIndexAtPoint(p);
+    if (componentIndex != -1 && layoutOrderList.size() > 0)
+      insertDraggableComponent(component, componentIndex);
+    else
+      insertDraggableComponent(component, -1);
+  }
+
+  public void selectDraggableComponent(DraggableComponent component) {
+    if (component == null) {
+      if (selectedComponent != null) {
+        DraggableComponent oldSelected = selectedComponent;
+        selectedComponent = null;
+        fireSelectedEvent(selectedComponent, oldSelected);
+        //componentBox.repaint();
+      }
+    }
+    else
+      component.select();
+  }
+
+  public void removeDraggableComponent(DraggableComponent component) {
+    if (component != null && draggableComponentList.contains(component)) {
+      //component.abortDrag();
+      int index = layoutOrderList.indexOf(component.getComponent());
+      component.removeListener(draggableComponentListener);
+      if (component == topComponent)
+        topComponent = null;
+      if (layoutOrderList.size() > 1 && selectedComponent != null) {
+        if (selectedComponent == component) {
+          if (autoSelect) {
+            int selectIndex = findSelectableComponentIndex(index);
+            if (selectIndex > -1)
+              selectDraggableComponent(findDraggableComponent((Component) layoutOrderList.get(selectIndex)));
+            else
+              selectedComponent = null;
+          }
+          else {
+            selectDraggableComponent(null);
+          }
+        }
+      }
+      else {
+        if (selectedComponent != null) {
+          DraggableComponent oldSelected = selectedComponent;
+          selectedComponent = null;
+          fireSelectedEvent(selectedComponent, oldSelected);
+        }
+      }
+      draggableComponentList.remove(component);
+      layoutOrderList.remove(component.getComponent());
+      componentBox.remove(component.getComponent());
+      componentBox.revalidate();
+      //componentBox.validate();
+      component.setLayoutOrderList(null);
+
+      sortComponentList(!descendingSortOrder);
+
+      updateScrollButtons();
+
+      fireRemovedEvent(component);
+    }
+  }
+
+  public boolean containsDraggableComponent(DraggableComponent component) {
+    return draggableComponentList.contains(component);
+  }
+
+  public DraggableComponent getSelectedDraggableComponent() {
+    return selectedComponent;
+  }
+
+  public int getDraggableComponentCount() {
+    return layoutOrderList.size();
+  }
+
+  public DraggableComponent getDraggableComponentAt(int index) {
+    return index < layoutOrderList.size() ? findDraggableComponent((Component) layoutOrderList.get(index)) : null;
+  }
+
+  public int getDraggableComponentIndex(DraggableComponent component) {
+    return layoutOrderList.indexOf(component.getComponent());
+  }
+
+  public Object[] getDraggableComponents() {
+    return draggableComponentList.toArray();
+  }
+
+  public Component[] getBoxComponents() {
+    return componentBox.getComponents();
+  }
+
+  public boolean getDepthSortOrder() {
+    return descendingSortOrder;
+  }
+
+  public void setDepthSortOrder(boolean descending) {
+    if (descending != this.descendingSortOrder) {
+      this.descendingSortOrder = descending;
+      sortComponentList(!descending);
+      doSort();
+    }
+  }
+
+  public boolean isScrollEnabled() {
+    return scrollEnabled;
+  }
+
+  public void setScrollEnabled(boolean scrollEnabled) {
+    if (scrollEnabled != this.scrollEnabled) {
+      this.scrollEnabled = scrollEnabled;
+      initialize();
+    }
+  }
+
+  public int getScrollOffset() {
+    return scrollOffset;
+  }
+
+  public void setScrollOffset(int scrollOffset) {
+    if (scrollOffset != this.scrollOffset) {
+      this.scrollOffset = scrollOffset;
+      if (scrollEnabled)
+        ((ScrollableBox) componentContainer).setScrollOffset(scrollOffset);
+    }
+  }
+
+  public int getComponentSpacing() {
+    return getDirectionLayout().getComponentSpacing();
+  }
+
+  public void setComponentSpacing(int componentSpacing) {
+    if (componentSpacing != getDirectionLayout().getComponentSpacing()) {
+      if (getComponentSpacing() < 0 && componentSpacing >= 0) {
+        DraggableComponent tmp = topComponent;
+        sortComponentList(false);
+        topComponent = tmp;
+      }
+      getDirectionLayout().setComponentSpacing(componentSpacing);
+      sortComponentList(!descendingSortOrder);
+      componentBox.revalidate();
+    }
+  }
+
+  public boolean isEnsureSelectedVisible() {
+    return ensureSelectedVisible;
+  }
+
+  public void setEnsureSelectedVisible(boolean ensureSelectedVisible) {
+    this.ensureSelectedVisible = ensureSelectedVisible;
+  }
+
+  public boolean isAutoSelect() {
+    return autoSelect;
+  }
+
+  public void setAutoSelect(boolean autoSelect) {
+    this.autoSelect = autoSelect;
+  }
+
+  public Direction getComponentDirection() {
+    return componentDirection;
+  }
+
+  public void setComponentDirection(Direction componentDirection) {
+    if (componentDirection != this.componentDirection) {
+      this.componentDirection = componentDirection;
+      getDirectionLayout().setDirection(componentDirection == Direction.UP ? Direction.RIGHT : componentDirection == Direction.LEFT ? Direction.DOWN : componentDirection == Direction.DOWN
+                                                                                                                                    ?
+                                                                                                                                     Direction.RIGHT
+                                                                                                                                     :
+                                                                                                                                       Direction.DOWN);
+      if (scrollEnabled) {
+        scrollButtonBox.setVertical(componentDirection.isHorizontal());
+        ((ScrollableBox) componentContainer).setVertical(componentDirection.isHorizontal());
+      }
+    }
+  }
+
+  public void setTopComponent(DraggableComponent topComponent) {
+    if (topComponent != this.topComponent) {
+      this.topComponent = topComponent;
+
+      sortComponentList(!descendingSortOrder);
+    }
+  }
+
+  public ScrollButtonBox getScrollButtonBox() {
+    return scrollButtonBox;
+  }
+
+  public JComponent getOuterParentArea() {
+    return outerParentArea;
+  }
+
+  public void setOuterParentArea(JComponent outerParentArea) {
+    this.outerParentArea = outerParentArea;
+  }
+
+  public void dragDraggableComponent(DraggableComponent component, Point p) {
+    if (draggableComponentList.contains(component)) {
+      component.drag(SwingUtilities.convertPoint(this, p, component.getComponent()));
+    }
+
+    //component.drag(SwingUtilities.convertPoint(this, p, component.getComponent()));
+  }
+
+  public Dimension getMaximumSize() {
+    if (scrollEnabled)
+      return getPreferredSize();
+
+    if (componentDirection == Direction.LEFT || componentDirection == Direction.RIGHT)
+      return new Dimension((int) getPreferredSize().getWidth(), (int) super.getMaximumSize().getHeight());
+
+    return new Dimension((int) super.getMaximumSize().getWidth(), (int) getPreferredSize().getHeight());
+
+  }
+
+  public Dimension getInnerSize() {
+    boolean mustSort = this.mustSort;
+    this.mustSort = false;
+    Dimension d = scrollEnabled ? componentBox.getPreferredSize() : componentBox.getSize();
+    this.mustSort = mustSort;
+    return d;
+  }
+
+  public void scrollToVisible(final DraggableComponent c) {
+    SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        if (scrollEnabled) {
+          ((ScrollableBox) componentContainer).ensureVisible(layoutOrderList.indexOf(c.getComponent()));
+        }
+      }
+    });
+  }
+
+  // Prevents focus problems when adding/removing focused component while sorting when spacing < 0
+  private void setIgnoreAddRemoveNotify(boolean ignore) {
+    for (int i = 0; i < draggableComponentList.size(); i++)
+      ((DraggableComponent) draggableComponentList.get(i)).setIgnoreAddNotify(ignore);
+  }
+
+  private void doSort() {
+    if (mustSort && getComponentSpacing() < 0 && componentBox.getComponentCount() > 0) {
+      setIgnoreAddRemoveNotify(true);
+
+      mustSort = false;
+      Component c;
+      Component tc = topComponent != null ? topComponent.getComponent() : null;
+
+      //componentBoxEnabled = false;
+
+      //long millis = System.currentTimeMillis();
+
+      int index = 0;
+      if (tc != null) {
+        if (componentBox.getComponent(0) != tc) {
+          componentBox.remove(tc);
+          componentBox.add(tc, index);
+        }
+        index++;
+      }
+
+      int switc = 0;
+      int size = layoutOrderList.size();
+      for (int i = 0; i < size; i++) {
+        c = (Component) layoutOrderList.get(doReverseSort ? size - i - 1 : i);
+        if (c != tc) {
+          if (componentBox.getComponent(index) != c) {
+            switc++;
+            componentBox.remove(c);
+            componentBox.add(c, index);
+          }
+          index++;
+        }
+      }
+
+      setIgnoreAddRemoveNotify(false);
+
+      /*System.out.print("  Box:   ");
+      for (int i = 0; i < componentBox.getComponentCount(); i++)
+        System.out.print(componentBox.getComponent(i) + "  ");
+      System.out.println();
+      System.out.print("  Order: ");
+      for (int i = 0; i < layoutOrderList.size(); i++)
+        System.out.print(layoutOrderList.get(i) + "  ");*/
+      //System.out.println();
+      /*			long millis = System.currentTimeMillis();
+			componentBox.removeAll();
+
+			if (tc != null)
+				componentBox.add(tc);
+
+			int size = layoutOrderList.size();
+			for (int i = 0; i < size; i++) {
+				c = (Component) layoutOrderList.get(doReverseSort ? size - i - 1 : i);
+				if (c != tc)
+					componentBox.add(c);
+			}*/
+
+      //componentBoxEnabled = true;
+
+      //System.out.println("Sorting " + scount++ + "  time: " + (System.currentTimeMillis() - millis) + "  Sorted: " + switc);
+    }
+  }
+
+  private void sortComponentList(boolean reverseSort) {
+    this.doReverseSort = reverseSort;
+    mustSort = true;
+  }
+
+  private int getComponentIndexAtPoint(Point p) {
+    JComponent c = null;
+    Point p2 = SwingUtilities.convertPoint(this, p, componentBox);
+    Point p3 = SwingUtilities.convertPoint(componentBox, p, outerParentArea);
+    if (outerParentArea.contains(p3))
+      c = (JComponent) ComponentUtil.getChildAtLine(componentBox,
+          p2,
+          getDirectionLayout().getDirection().isHorizontal());
+
+    return layoutOrderList.indexOf(c);
+  }
+
+  private void doSelectComponent(DraggableComponent component) {
+    if (selectedComponent != null) {
+      DraggableComponent oldSelected = selectedComponent;
+      selectedComponent = component;
+      ensureSelectedVisible();
+      fireSelectedEvent(selectedComponent, oldSelected);
+    }
+    else {
+      selectedComponent = component;
+      ensureSelectedVisible();
+      fireSelectedEvent(selectedComponent, null);
+    }
+  }
+
+  private int findSelectableComponentIndex(int index) {
+    int selectIndex = -1;
+    int k;
+    for (int i = 0; i < layoutOrderList.size(); i++) {
+      if ((findDraggableComponent((Component) layoutOrderList.get(i))).isEnabled() && i != index) {
+        k = selectIndex;
+        selectIndex = i;
+        if (k < index && selectIndex > index)
+          return selectIndex;
+        else if (k > index && selectIndex > index)
+          return k;
+      }
+    }
+
+    return selectIndex;
+  }
+
+  private DraggableComponent findDraggableComponent(Component c) {
+    for (int i = 0; i < draggableComponentList.size(); i++)
+      if (((DraggableComponent) draggableComponentList.get(i)).getComponent() == c)
+        return (DraggableComponent) draggableComponentList.get(i);
+
+    return null;
+  }
+
+  private DirectionLayout getDirectionLayout() {
+    return (DirectionLayout) componentBox.getLayout();
+  }
+
+  private void initialize() {
+    if (componentContainer != null)
+      remove(componentContainer);
+
+    DirectionLayout layout = getDirectionLayout();
+    layout.setCompressing(!scrollEnabled);
+
+    if (scrollEnabled) {
+      if (useDefaultScrollButtons)
+        scrollButtonBox = new ScrollButtonBox(componentDirection.isHorizontal(), iconSize);
+      else
+        scrollButtonBox = new ScrollButtonBox(componentDirection.isHorizontal(), null, null, null, null);
+
+      final ScrollableBox scrollableBox = new ScrollableBox(componentBox,
+          componentDirection.isHorizontal(),
+          scrollOffset);
+      scrollableBox.setLayoutOrderList(layoutOrderList);
+      scrollButtonBox.addListener(new ScrollButtonBoxListener() {
+        public void scrollButton1() {
+          scrollableBox.scrollLeft(1);
+        }
+
+        public void scrollButton2() {
+          scrollableBox.scrollRight(1);
+        }
+      });
+
+      scrollableBox.addComponentListener(new ComponentAdapter() {
+        public void componentResized(ComponentEvent e) {
+          scrollButtonBox.setButton1Enabled(!scrollableBox.isLeftEnd());
+          scrollButtonBox.setButton2Enabled(!scrollableBox.isRightEnd());
+        }
+      });
+
+      scrollButtonBox.setButton1Enabled(!scrollableBox.isLeftEnd());
+      scrollButtonBox.setButton2Enabled(!scrollableBox.isRightEnd());
+
+      scrollableBox.addScrollableBoxListener(new ScrollableBoxListener() {
+        public void scrolledLeft(ScrollableBox box) {
+          scrollButtonBox.setButton1Enabled(!box.isLeftEnd());
+          scrollButtonBox.setButton2Enabled(true);
+        }
+
+        public void scrolledRight(ScrollableBox box) {
+          scrollButtonBox.setButton1Enabled(true);
+          scrollButtonBox.setButton2Enabled(!box.isRightEnd());
+        }
+
+        public void changed(ScrollableBox box) {
+          fireChangedEvent();
+        }
+      });
+      componentContainer = scrollableBox;
+    }
+    else {
+      scrollButtonBox = null;
+      componentContainer = componentBox;
+    }
+
+    componentContainer.setAlignmentY(0);
+    add(componentContainer, BorderLayout.CENTER);
+
+    revalidate();
+  }
+
+  private void ensureSelectedVisible() {
+    SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        if (scrollEnabled && ensureSelectedVisible && selectedComponent != null) {
+          //componentContainer.validate();
+          ((ScrollableBox) componentContainer).ensureVisible(layoutOrderList.indexOf(selectedComponent.getComponent()));
+        }
+      }
+    });
+  }
+
+  private void fireDraggedEvent(DraggableComponentEvent e) {
+    if (listeners != null) {
+      DraggableComponentBoxEvent event = new DraggableComponentBoxEvent(this,
+          e.getSource(),
+          e,
+          SwingUtilities.convertPoint(
+              e.getSource().getComponent(),
+              e.getMouseEvent().getPoint(),
+              this));
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentBoxListener) l[i]).componentDragged(event);
+    }
+  }
+
+  private void fireDroppedEvent(DraggableComponentEvent e) {
+    if (listeners != null) {
+      DraggableComponentBoxEvent event = new DraggableComponentBoxEvent(this,
+          e.getSource(),
+          e,
+          SwingUtilities.convertPoint(
+              e.getSource().getComponent(),
+              e.getMouseEvent().getPoint(),
+              this));
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentBoxListener) l[i]).componentDropped(event);
+    }
+  }
+
+  private void fireNotDroppedEvent(DraggableComponentEvent e) {
+    if (listeners != null) {
+      DraggableComponentBoxEvent event = new DraggableComponentBoxEvent(this, e.getSource(), e);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentBoxListener) l[i]).componentDragAborted(event);
+    }
+  }
+
+  private void fireSelectedEvent(DraggableComponent component, DraggableComponent oldDraggableComponent) {
+    if (listeners != null) {
+      DraggableComponentBoxEvent event = new DraggableComponentBoxEvent(this, component, oldDraggableComponent);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentBoxListener) l[i]).componentSelected(event);
+    }
+  }
+
+  private void fireAddedEvent(DraggableComponent component) {
+    if (listeners != null) {
+      DraggableComponentBoxEvent event = new DraggableComponentBoxEvent(this, component);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentBoxListener) l[i]).componentAdded(event);
+    }
+  }
+
+  private void fireRemovedEvent(DraggableComponent component) {
+    if (listeners != null) {
+      DraggableComponentBoxEvent event = new DraggableComponentBoxEvent(this, component);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentBoxListener) l[i]).componentRemoved(event);
+    }
+  }
+
+  private void fireChangedEvent(DraggableComponentEvent e) {
+    if (listeners != null) {
+      DraggableComponentBoxEvent event = new DraggableComponentBoxEvent(this, e.getSource(), e);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentBoxListener) l[i]).changed(event);
+    }
+  }
+
+  private void fireChangedEvent() {
+    if (listeners != null) {
+      DraggableComponentBoxEvent event = new DraggableComponentBoxEvent(this);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((DraggableComponentBoxListener) l[i]).changed(event);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/draggable/DraggableComponentBoxAdapter.java b/src/net/infonode/gui/draggable/DraggableComponentBoxAdapter.java
new file mode 100644
index 0000000..9897412
--- /dev/null
+++ b/src/net/infonode/gui/draggable/DraggableComponentBoxAdapter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DraggableComponentBoxAdapter.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.gui.draggable;
+
+public class DraggableComponentBoxAdapter implements DraggableComponentBoxListener {
+  public void componentDragged(DraggableComponentBoxEvent event) {
+  }
+
+  public void componentDropped(DraggableComponentBoxEvent event) {
+  }
+
+  public void componentDragAborted(DraggableComponentBoxEvent event) {
+  }
+
+  public void componentSelected(DraggableComponentBoxEvent event) {
+  }
+
+  public void componentRemoved(DraggableComponentBoxEvent event) {
+  }
+
+  public void componentAdded(DraggableComponentBoxEvent event) {
+  }
+
+  public void changed(DraggableComponentBoxEvent event) {
+  }
+}
diff --git a/src/net/infonode/gui/draggable/DraggableComponentBoxEvent.java b/src/net/infonode/gui/draggable/DraggableComponentBoxEvent.java
new file mode 100644
index 0000000..a1aa266
--- /dev/null
+++ b/src/net/infonode/gui/draggable/DraggableComponentBoxEvent.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DraggableComponentBoxEvent.java,v 1.4 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.draggable;
+
+import java.awt.*;
+
+public class DraggableComponentBoxEvent {
+  private DraggableComponentBox source;
+  private DraggableComponent draggableComponent;
+  private DraggableComponent oldDraggableComponent;
+  private DraggableComponentEvent draggableComponentEvent;
+  private Point draggableComponentBoxPoint;
+
+  public DraggableComponentBoxEvent(DraggableComponentBox source) {
+    this(source, null);
+  }
+
+  public DraggableComponentBoxEvent(DraggableComponentBox source, DraggableComponent component) {
+    this(source, component, null, null);
+  }
+
+  public DraggableComponentBoxEvent(DraggableComponentBox source,
+                                    DraggableComponent component,
+                                    DraggableComponentEvent event) {
+    this(source, component, event, null);
+  }
+
+  public DraggableComponentBoxEvent(DraggableComponentBox source,
+                                    DraggableComponent component,
+                                    DraggableComponentEvent event,
+                                    Point point) {
+    this.source = source;
+    this.draggableComponent = component;
+    this.draggableComponentEvent = event;
+    this.draggableComponentBoxPoint = point;
+  }
+
+  public DraggableComponentBoxEvent(DraggableComponentBox source,
+                                    DraggableComponent component,
+                                    DraggableComponent oldDraggableComponent) {
+    this(source, component);
+    this.oldDraggableComponent = oldDraggableComponent;
+  }
+
+  public DraggableComponentBox getSource() {
+    return source;
+  }
+
+  public DraggableComponent getDraggableComponent() {
+    return draggableComponent;
+  }
+
+  public DraggableComponent getOldDraggableComponent() {
+    return oldDraggableComponent;
+  }
+
+  public Point getDraggableComponentBoxPoint() {
+    return draggableComponentBoxPoint;
+  }
+
+  public DraggableComponentEvent getDraggableComponentEvent() {
+    return draggableComponentEvent;
+  }
+}
diff --git a/src/net/infonode/gui/draggable/DraggableComponentBoxListener.java b/src/net/infonode/gui/draggable/DraggableComponentBoxListener.java
new file mode 100644
index 0000000..f62e811
--- /dev/null
+++ b/src/net/infonode/gui/draggable/DraggableComponentBoxListener.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DraggableComponentBoxListener.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.gui.draggable;
+
+public interface DraggableComponentBoxListener {
+  public void componentDragged(DraggableComponentBoxEvent event);
+
+  public void componentDropped(DraggableComponentBoxEvent event);
+
+  public void componentDragAborted(DraggableComponentBoxEvent event);
+
+  public void componentSelected(DraggableComponentBoxEvent event);
+
+  public void componentRemoved(DraggableComponentBoxEvent event);
+
+  public void componentAdded(DraggableComponentBoxEvent event);
+
+  public void changed(DraggableComponentBoxEvent event);
+}
diff --git a/src/net/infonode/gui/draggable/DraggableComponentEvent.java b/src/net/infonode/gui/draggable/DraggableComponentEvent.java
new file mode 100644
index 0000000..e2f04a5
--- /dev/null
+++ b/src/net/infonode/gui/draggable/DraggableComponentEvent.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DraggableComponentEvent.java,v 1.3 2004/11/25 13:06:42 jesper Exp $
+package net.infonode.gui.draggable;
+
+import java.awt.event.MouseEvent;
+
+public class DraggableComponentEvent {
+  public static final int TYPE_UNDEFINED = -1;
+  public static final int TYPE_MOVED = 0;
+  public static final int TYPE_PRESSED = 1;
+  public static final int TYPE_RELEASED = 2;
+  public static final int TYPE_ENABLED = 3;
+  public static final int TYPE_DISABLED = 4;
+
+  private DraggableComponent source;
+  private int type = TYPE_UNDEFINED;
+  private MouseEvent mouseEvent;
+
+  public DraggableComponentEvent(DraggableComponent source) {
+    this(source, null);
+  }
+
+  public DraggableComponentEvent(DraggableComponent source, MouseEvent mouseEvent) {
+    this(source, TYPE_UNDEFINED, mouseEvent);
+  }
+
+  public DraggableComponentEvent(DraggableComponent source, int type) {
+    this(source, type, null);
+  }
+
+  public DraggableComponentEvent(DraggableComponent source, int type, MouseEvent mouseEvent) {
+    this.source = source;
+    this.type = type;
+    this.mouseEvent = mouseEvent;
+  }
+
+  public DraggableComponent getSource() {
+    return source;
+  }
+
+  public int getType() {
+    return type;
+  }
+
+  public MouseEvent getMouseEvent() {
+    return mouseEvent;
+  }
+
+}
diff --git a/src/net/infonode/gui/draggable/DraggableComponentListener.java b/src/net/infonode/gui/draggable/DraggableComponentListener.java
new file mode 100644
index 0000000..f57bb8b
--- /dev/null
+++ b/src/net/infonode/gui/draggable/DraggableComponentListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DraggableComponentListener.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.gui.draggable;
+
+public interface DraggableComponentListener {
+  public void changed(DraggableComponentEvent event);
+
+  public void selected(DraggableComponentEvent event);
+
+  public void dragged(DraggableComponentEvent event);
+
+  public void dropped(DraggableComponentEvent event);
+
+  public void dragAborted(DraggableComponentEvent event);
+}
diff --git a/src/net/infonode/gui/hover/CompoundHoverListener.java b/src/net/infonode/gui/hover/CompoundHoverListener.java
new file mode 100644
index 0000000..314c692
--- /dev/null
+++ b/src/net/infonode/gui/hover/CompoundHoverListener.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: CompoundHoverListener.java,v 1.3 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.hover;
+
+
+/**
+ * CompoundHoverListener takes the two given hover listeners and calls the
+ * first hover listener and then the second when the mouse is hovering.
+ * When the mouse is no longer hovering, the second listener is called
+ * and then the first listener is called.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class CompoundHoverListener implements HoverListener {
+  private HoverListener firstListener;
+  private HoverListener secondListener;
+
+  /**
+   * Creates a CompoundHoverListener
+   *
+   * @param firstListener  the first hover listener
+   * @param secondListener the second hover listener
+   */
+  public CompoundHoverListener(HoverListener firstListener, HoverListener secondListener) {
+    this.firstListener = firstListener;
+    this.secondListener = secondListener;
+  }
+
+  /**
+   * Gets the first hover listener
+   *
+   * @return the hover listener
+   */
+  public HoverListener getFirstListener() {
+    return firstListener;
+  }
+
+  /**
+   * Gets the second hover listener
+   *
+   * @return the hover listener
+   */
+  public HoverListener getSecondListener() {
+    return secondListener;
+  }
+
+  public void mouseEntered(HoverEvent event) {
+    firstListener.mouseEntered(event);
+    secondListener.mouseEntered(event);
+  }
+
+  public void mouseExited(HoverEvent event) {
+    secondListener.mouseExited(event);
+    firstListener.mouseExited(event);
+  }
+}
diff --git a/src/net/infonode/gui/hover/HoverEvent.java b/src/net/infonode/gui/hover/HoverEvent.java
new file mode 100644
index 0000000..4664d0f
--- /dev/null
+++ b/src/net/infonode/gui/hover/HoverEvent.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: HoverEvent.java,v 1.5 2005/02/16 11:28:11 jesper Exp $
+
+package net.infonode.gui.hover;
+
+import java.awt.*;
+
+/**
+ * HoverEvent contains information about a component hovered by the
+ * mouse
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class HoverEvent {
+  private Component source;
+
+  /**
+   * Creates a HoverEvent
+   *
+   * @param source the hoverable component that is the source for this event
+   */
+  public HoverEvent(Component source) {
+    this.source = source;
+  }
+
+  /**
+   * Gets the hoverable component that is the source for this event
+   *
+   * @return the hoverable component
+   */
+  public Component getSource() {
+    return source;
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/hover/HoverListener.java b/src/net/infonode/gui/hover/HoverListener.java
new file mode 100644
index 0000000..8ebee3a
--- /dev/null
+++ b/src/net/infonode/gui/hover/HoverListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: HoverListener.java,v 1.9 2005/02/16 11:28:11 jesper Exp $
+
+package net.infonode.gui.hover;
+
+/**
+ * HoverListener interface for receiving events when a hoverable component is
+ * hovered by the mouse.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ */
+public interface HoverListener {
+  /**
+   * Called when the mouse enters the hoverable component
+   *
+   * @param event the hover event
+   */
+  void mouseEntered(HoverEvent event);
+
+  /**
+   * Called when the mouse exits the hoverable component
+   *
+   * @param event the hover event
+   */
+  void mouseExited(HoverEvent event);
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/hover/action/DelayedHoverExitAction.java b/src/net/infonode/gui/hover/action/DelayedHoverExitAction.java
new file mode 100644
index 0000000..d99eb37
--- /dev/null
+++ b/src/net/infonode/gui/hover/action/DelayedHoverExitAction.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DelayedHoverExitAction.java,v 1.5 2005/02/16 11:28:11 jesper Exp $
+
+package net.infonode.gui.hover.action;
+
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+
+/**
+ * @author johan
+ */
+public class DelayedHoverExitAction implements HoverListener {
+
+  private HashMap timers = new HashMap();
+  private HashMap exitEvents = new HashMap();
+
+  private HoverListener action;
+  private int delay;
+
+  public DelayedHoverExitAction(HoverListener action, int delay) {
+    this.action = action;
+    this.delay = delay;
+  }
+
+  public int getDelay() {
+    return delay;
+  }
+
+  public void setDelay(int delay) {
+    this.delay = delay;
+  }
+
+  public HoverListener getHoverAction() {
+    return action;
+  }
+
+  public void forceExit(Component component) {
+    if (timers.containsKey(component)) {
+      ((Timer) timers.get(component)).stop();
+      timers.remove(component);
+      HoverEvent event = (HoverEvent) exitEvents.get(component);
+      exitEvents.remove(component);
+      action.mouseExited(event);
+    }
+  }
+
+  public void mouseEntered(HoverEvent event) {
+    final Component c = event.getSource();
+
+    if (timers.containsKey(c))
+      ((Timer) timers.get(c)).stop();
+    else {
+      Timer t = new Timer(delay, new ActionListener() {
+        public void actionPerformed(ActionEvent e) {
+          forceExit(c);
+        }
+      });
+      t.setRepeats(false);
+      timers.put(c, t);
+
+      action.mouseEntered(event);
+    }
+  }
+
+  public void mouseExited(HoverEvent event) {
+    exitEvents.put(event.getSource(), event);
+    ((Timer) timers.get(event.getSource())).restart();
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/hover/action/package.html b/src/net/infonode/gui/hover/action/package.html
new file mode 100644
index 0000000..5eb71b3
--- /dev/null
+++ b/src/net/infonode/gui/hover/action/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Hover action classes
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/gui/hover/hoverable/HoverManager.java b/src/net/infonode/gui/hover/hoverable/HoverManager.java
new file mode 100644
index 0000000..809ce22
--- /dev/null
+++ b/src/net/infonode/gui/hover/hoverable/HoverManager.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: HoverManager.java,v 1.16 2008/11/28 10:00:35 jesper Exp $
+
+package net.infonode.gui.hover.hoverable;
+
+import java.awt.*;
+import java.awt.event.AWTEventListener;
+import java.awt.event.HierarchyEvent;
+import java.awt.event.HierarchyListener;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import javax.swing.SwingUtilities;
+import javax.swing.event.MouseInputAdapter;
+
+import net.infonode.gui.ComponentUtil;
+import net.infonode.util.ArrayUtil;
+
+/**
+ * @author johan
+ */
+public class HoverManager {
+  private static HoverManager INSTANCE = new HoverManager();
+
+  private final HierarchyListener hierarchyListener = new HierarchyListener() {
+    public void hierarchyChanged(final HierarchyEvent e) {
+      SwingUtilities.invokeLater(new Runnable() {
+        public void run() {
+          if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
+            if (((Component) e.getSource()).isShowing()) {
+              addHoverListeners((Hoverable) e.getSource());
+            } else {
+              removeHoverListeners((Hoverable) e.getSource());
+            }
+          }
+        }
+      });
+    }
+  };
+
+  private final MouseInputAdapter mouseAdapter = new MouseInputAdapter() {
+  };
+
+  private final HashSet hoverableComponents = new HashSet();
+
+  private final ArrayList enteredComponents = new ArrayList();
+
+  private boolean enabled = true;
+
+  private boolean hasPermission = true;
+
+  private boolean active = true;
+
+  private boolean gotEnterAfterExit = false;
+
+  private boolean isDrag = false;
+
+  private final AWTEventListener eventListener = new AWTEventListener() {
+    public void eventDispatched(final AWTEvent e) {
+      if (active) {
+        HoverManager.this.eventDispatched(e);
+      }
+    }
+  };
+
+  private void eventDispatched(final AWTEvent e) {
+    if (e.getSource() instanceof Component /* Fix for TrayIcon in 1.6. Only handle real components */ && e instanceof MouseEvent) {
+      MouseEvent event = (MouseEvent) e;
+
+      if (event.getID() == MouseEvent.MOUSE_PRESSED || event.getID() == MouseEvent.MOUSE_RELEASED) {
+        handleButtonEvent(event);
+      } else if (event.getID() == MouseEvent.MOUSE_ENTERED || event.getID() == MouseEvent.MOUSE_MOVED) {
+        handleEnterEvent(event);
+      } else if (event.getID() == MouseEvent.MOUSE_EXITED) {
+        handleExitEvent(event);
+      } else if (event.getID() == MouseEvent.MOUSE_DRAGGED) {
+        isDrag = true;
+      }
+    }
+  }
+
+  private void handleButtonEvent(MouseEvent event) {
+    if (event.getID() == MouseEvent.MOUSE_PRESSED && event.getButton() == MouseEvent.BUTTON1) {
+      enabled = false;
+      isDrag = false;
+    } else if (!enabled && event.getID() == MouseEvent.MOUSE_RELEASED) {
+      enabled = true;
+
+      if (isDrag) {
+        final Component top = ComponentUtil.getTopLevelAncestor((Component) event.getSource());
+        if (top == null)
+          exitAll();
+        else if (!((Component) event.getSource()).contains(event.getPoint())) {
+          final Point p = SwingUtilities.convertPoint((Component) event.getSource(), event.getPoint(), top);
+          if (!top.contains(p.x, p.y)) {
+            exitAll();
+          } else if (top instanceof Container) {
+            SwingUtilities.invokeLater(new Runnable() {
+              public void run() {
+                SwingUtilities.invokeLater(new Runnable() {
+                  public void run() {
+                    Component c = ComponentUtil.findComponentUnderGlassPaneAt(p, top);
+
+                    if (c != null) {
+                      Point p2 = SwingUtilities.convertPoint(top, p, c);
+                      eventDispatched(new MouseEvent(c, MouseEvent.MOUSE_ENTERED, 0, 0, p2.x, p2.y, 0, false));
+                    }
+                  }
+                });
+              }
+            });
+          }
+        }
+      }
+    }
+  }
+
+  private void handleEnterEvent(MouseEvent event) {
+    gotEnterAfterExit = true;
+
+    ArrayList exitables = new ArrayList(enteredComponents);
+    ArrayList enterables = new ArrayList();
+
+    Component c = (Component) event.getSource();
+    while (c != null) {
+      if (hoverableComponents.contains(c)) {
+        exitables.remove(c);
+        enterables.add(c);
+      }
+
+      c = c.getParent();
+    }
+
+    if (enterables.size() > 0) {
+      Object obj[] = enterables.toArray();
+      for (int i = obj.length - 1; i >= 0; i--) {
+        if (!((Hoverable) obj[i]).acceptHover(enterables)) {
+          enterables.remove(obj[i]);
+          exitables.add(obj[i]);
+        }
+      }
+    }
+
+    for (int i = exitables.size() - 1; i >= 0; i--) {
+      dispatchExit((Hoverable) exitables.get(i));
+    }
+
+    for (int i = enterables.size() - 1; i >= 0; i--) {
+      dispatchEnter((Hoverable) enterables.get(i));
+    }
+  }
+
+  private void handleExitEvent(MouseEvent event) {
+    gotEnterAfterExit = false;
+
+    SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        if (!gotEnterAfterExit)
+          exitAll();
+      }
+    });
+  }
+
+  public static HoverManager getInstance() {
+    return INSTANCE;
+  }
+
+  private HoverManager() {
+    try {
+      SecurityManager sm = System.getSecurityManager();
+      if (sm != null)
+        sm.checkPermission(new AWTPermission("listenToAllAWTEvents"));
+    } catch (SecurityException e) {
+      hasPermission = false;
+    }
+  }
+
+  private void exitAll() {
+    gotEnterAfterExit = false;
+    Object[] obj = enteredComponents.toArray();
+    for (int i = obj.length - 1; i >= 0; i--) {
+      dispatchExit((Hoverable) obj[i]);
+    }
+  }
+
+  public void init() {
+    gotEnterAfterExit = false;
+    isDrag = false;
+    enabled = true;
+  }
+
+  public void setEventListeningActive(boolean active) {
+    this.active = active;
+  }
+
+  public void dispatchEvent(MouseEvent event) {
+    eventDispatched(event);
+  }
+
+  private void addHoverListeners(Hoverable hoverable) {
+    if (hoverableComponents.add(hoverable)) {
+      Component c = (Component) hoverable;
+      c.addMouseListener(mouseAdapter);
+      c.addMouseMotionListener(mouseAdapter);
+
+      if (active && hoverableComponents.size() == 1) {
+        try {
+          Toolkit.getDefaultToolkit().addAWTEventListener(eventListener, AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
+          hasPermission = true;
+        } catch (SecurityException e) {
+          hasPermission = false;
+        }
+      }
+    }
+  }
+
+  private void removeHoverListeners(Hoverable hoverable) {
+    if (hoverableComponents.remove(hoverable)) {
+      ((Component) hoverable).removeMouseListener(mouseAdapter);
+      ((Component) hoverable).removeMouseMotionListener(mouseAdapter);
+      dispatchExit(hoverable);
+
+      if (hasPermission && hoverableComponents.size() == 0) {
+        Toolkit.getDefaultToolkit().removeAWTEventListener(eventListener);
+      }
+    }
+  }
+
+  public void addHoverable(Hoverable hoverable) {
+    if (hoverable instanceof Component) {
+      Component c = (Component) hoverable;
+
+      if (ArrayUtil.contains(c.getHierarchyListeners(), hierarchyListener))
+        return;
+
+      c.addHierarchyListener(hierarchyListener);
+
+      if (c.isShowing())
+        addHoverListeners(hoverable);
+    }
+  }
+
+  public void removeHoverable(Hoverable hoverable) {
+    Component c = (Component) hoverable;
+    c.removeHierarchyListener(hierarchyListener);
+    removeHoverListeners(hoverable);
+  }
+
+  public boolean isHovered(Hoverable c) {
+    return enteredComponents.contains(c);
+  }
+
+  public boolean isEventListeningActive() {
+    return active && hasPermission;
+  }
+
+  private void dispatchEnter(Hoverable hoverable) {
+    if (enabled && !enteredComponents.contains(hoverable)) {
+      enteredComponents.add(hoverable);
+      hoverable.hoverEnter();
+    }
+  }
+
+  private void dispatchExit(Hoverable hoverable) {
+    if (enabled && enteredComponents.remove(hoverable))
+      hoverable.hoverExit();
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/hover/hoverable/Hoverable.java b/src/net/infonode/gui/hover/hoverable/Hoverable.java
new file mode 100644
index 0000000..9c9a8bd
--- /dev/null
+++ b/src/net/infonode/gui/hover/hoverable/Hoverable.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Hoverable.java,v 1.3 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.hover.hoverable;
+
+import java.util.ArrayList;
+
+/**
+ * @author johan
+ */
+public interface Hoverable {
+  boolean acceptHover(ArrayList enterableHoverables);
+
+  void hoverEnter();
+
+  void hoverExit();
+}
diff --git a/src/net/infonode/gui/hover/package.html b/src/net/infonode/gui/hover/package.html
new file mode 100644
index 0000000..7609887
--- /dev/null
+++ b/src/net/infonode/gui/hover/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Mouse hover listeners and hover events
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/gui/hover/panel/HoverableShapedPanel.java b/src/net/infonode/gui/hover/panel/HoverableShapedPanel.java
new file mode 100644
index 0000000..dbf1059
--- /dev/null
+++ b/src/net/infonode/gui/hover/panel/HoverableShapedPanel.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: HoverableShapedPanel.java,v 1.15 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.hover.panel;
+
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.gui.hover.hoverable.HoverManager;
+import net.infonode.gui.hover.hoverable.Hoverable;
+import net.infonode.gui.shaped.panel.ShapedPanel;
+
+import java.awt.*;
+import java.util.ArrayList;
+
+/**
+ * @author johan
+ */
+public class HoverableShapedPanel extends ShapedPanel implements Hoverable {
+  private HoverListener hoverListener;
+
+  private Component hoveredComponent;
+
+  private boolean hovered = false;
+
+  public HoverableShapedPanel(HoverListener listener) {
+    this(new BorderLayout(), listener, null);
+  }
+
+  public HoverableShapedPanel(LayoutManager l, HoverListener listener) {
+    this(l, listener, null);
+  }
+
+  public HoverableShapedPanel(LayoutManager l, HoverListener listener,
+                              final Component hoveredComponent) {
+    super(l);
+    this.hoveredComponent = hoveredComponent != null ? hoveredComponent : this;
+    HoverManager.getInstance().addHoverable(this);
+    setHoverListener(listener);
+  }
+
+  public HoverListener getHoverListener() {
+    return hoverListener;
+  }
+
+  public void setHoverListener(HoverListener newHoverListener) {
+    if (hoverListener != newHoverListener) {
+      HoverListener oldHoverListener = hoverListener;
+
+      // if (oldHoverListener != null && newHoverListener == null)
+      // HoverManager.getInstance().removeHoverable(this);
+
+      hoverListener = newHoverListener;
+
+      // if (oldHoverListener == null && newHoverListener != null)
+      // HoverManager.getInstance().addHoverable(this);
+
+      if (oldHoverListener != null && newHoverListener != null && hovered) {
+        HoverEvent event = new HoverEvent(hoveredComponent);
+        oldHoverListener.mouseExited(event);
+        newHoverListener.mouseEntered(event);
+      }
+    }
+  }
+
+  public void hoverEnter() {
+    if (hoverListener != null) {
+      hovered = true;
+      hoverListener.mouseEntered(new HoverEvent(HoverableShapedPanel.this.hoveredComponent));
+    }
+  }
+
+  public void hoverExit() {
+    if (hoverListener != null) {
+      hovered = false;
+      hoverListener.mouseExited(new HoverEvent(HoverableShapedPanel.this.hoveredComponent));
+    }
+  }
+
+  public Component getHoveredComponent() {
+    return hoveredComponent;
+  }
+
+  public boolean isHovered() {
+    return hovered;
+  }
+
+  public boolean acceptHover(ArrayList enterableHoverables) {
+    return true;
+  }
+}
diff --git a/src/net/infonode/gui/icon/EmptyIcon.java b/src/net/infonode/gui/icon/EmptyIcon.java
new file mode 100644
index 0000000..691f13b
--- /dev/null
+++ b/src/net/infonode/gui/icon/EmptyIcon.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: EmptyIcon.java,v 1.4 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.icon;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author Jesper Nordenberg
+ * @version $Revision: 1.4 $ $Date: 2005/02/16 11:28:11 $
+ */
+public class EmptyIcon implements Icon {
+  public static final EmptyIcon INSTANCE = new EmptyIcon();
+
+  private EmptyIcon() {
+  }
+
+  public void paintIcon(Component c, Graphics g, int x, int y) {
+  }
+
+  public int getIconWidth() {
+    return 0;
+  }
+
+  public int getIconHeight() {
+    return 0;
+  }
+}
diff --git a/src/net/infonode/gui/icon/IconProvider.java b/src/net/infonode/gui/icon/IconProvider.java
new file mode 100644
index 0000000..c700bbd
--- /dev/null
+++ b/src/net/infonode/gui/icon/IconProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: IconProvider.java,v 1.3 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.icon;
+
+import javax.swing.*;
+
+/**
+ * Interface for an object that provides an icon.
+ *
+ * @author Bjorn Lind
+ * @version $Revision: 1.3 $ $Date: 2005/02/16 11:28:11 $
+ */
+public interface IconProvider {
+  /**
+   * Gets the icon
+   *
+   * @return icon
+   */
+  public Icon getIcon();
+}
diff --git a/src/net/infonode/gui/icon/IconUtil.java b/src/net/infonode/gui/icon/IconUtil.java
new file mode 100644
index 0000000..44a7fa6
--- /dev/null
+++ b/src/net/infonode/gui/icon/IconUtil.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: IconUtil.java,v 1.5 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.icon;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class IconUtil {
+  private IconUtil() {
+  }
+
+  public static final Icon SMALL_ICON = new Icon() {
+    public int getIconHeight() {
+      return 1;
+    }
+
+    public int getIconWidth() {
+      return 1;
+    }
+
+    public void paintIcon(Component c, Graphics g, int x, int y) {
+    }
+  };
+
+  public static Icon copy(final Icon icon) {
+    return new Icon() {
+      public void paintIcon(Component c, Graphics g, int x, int y) {
+        icon.paintIcon(c, g, x, y);
+      }
+
+      public int getIconWidth() {
+        return icon.getIconWidth();
+      }
+
+      public int getIconHeight() {
+        return icon.getIconHeight();
+      }
+    };
+  }
+
+  public static Icon getIcon(Object object) {
+    return object == null ? null :
+           object instanceof AbstractButton ? (Icon) ((AbstractButton) object).getIcon() :
+           object instanceof Action ? (Icon) ((Action) object).getValue(Action.SMALL_ICON) :
+           object instanceof IconProvider ? ((IconProvider) object).getIcon() : null;
+  }
+
+  public static int getIconWidth(Object object) {
+    Icon icon = getIcon(object);
+    return icon == null ? 0 : icon.getIconWidth();
+  }
+
+  public static int getIconHeight(Object object) {
+    Icon icon = getIcon(object);
+    return icon == null ? 0 : icon.getIconHeight();
+  }
+
+  public static int getMaxIconWidth(Object[] objects) {
+    int max = 0;
+
+    for (int i = 0; i < objects.length; i++) {
+      int width = getIconWidth(objects[i]);
+
+      if (width > max) {
+        max = width;
+      }
+    }
+
+    return max;
+  }
+
+}
diff --git a/src/net/infonode/gui/icon/button/AbstractButtonIcon.java b/src/net/infonode/gui/icon/button/AbstractButtonIcon.java
new file mode 100644
index 0000000..3245ef4
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/AbstractButtonIcon.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractButtonIcon.java,v 1.10 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import net.infonode.gui.ComponentUtil;
+import net.infonode.util.ColorUtil;
+
+import javax.swing.*;
+import java.awt.*;
+import java.io.Serializable;
+
+public abstract class AbstractButtonIcon implements Icon, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private int size = 10;
+  private Color defaultColor = null;
+  private boolean shadowEnabled = true;
+  private float shadowStrength = 0.3f;
+  private boolean enabled = true;
+
+  public AbstractButtonIcon() {
+  }
+
+  public AbstractButtonIcon(Color color) {
+    this.defaultColor = color;
+  }
+
+  public AbstractButtonIcon(Color color, int size) {
+    this(color);
+    this.size = size;
+  }
+
+  public AbstractButtonIcon(int size) {
+    this(size, true);
+  }
+
+  public AbstractButtonIcon(int size, boolean enabled) {
+    this();
+    this.size = size;
+    this.enabled = enabled;
+  }
+
+  public int getIconWidth() {
+    return size;
+  }
+
+  public int getIconHeight() {
+    return size;
+  }
+
+  public boolean isShadowEnabled() {
+    return shadowEnabled;
+  }
+
+  public void setShadowEnabled(boolean shadowEnabled) {
+    this.shadowEnabled = shadowEnabled;
+  }
+
+  public float getShadowStrength() {
+    return shadowStrength;
+  }
+
+  public void setShadowStrength(float shadowStrength) {
+    this.shadowStrength = shadowStrength;
+  }
+
+  public void paintIcon(Component c, Graphics g, int x, int y) {
+    Color oldColor = g.getColor();
+    Color color = defaultColor == null ?
+                  (enabled ? c.getForeground() : UIManager.getColor("Button.disabledForeground")) :
+                  defaultColor;
+    if (color == null)
+      color = ColorUtil.blend(ComponentUtil.getBackgroundColor(c), c.getForeground(), 0.5f);
+
+    if (shadowEnabled) {
+      Color background = ComponentUtil.getBackgroundColor(c);
+      g.setColor(ColorUtil.blend(background == null ? Color.BLACK : background, Color.BLACK, shadowStrength));
+      paintIcon(c, g, x + 2, y + 2, x + size - 1, y + size - 1, true);
+      g.setColor(color);
+      paintIcon(c, g, x + 1, y + 1, x + size - 2, y + size - 2, false);
+    }
+    else {
+      g.setColor(color);
+      paintIcon(c, g, x, y, x + size - 1, y + size - 1, false);
+    }
+
+    g.setColor(oldColor);
+  }
+
+  protected void paintIcon(Component c, Graphics g, int x1, int y1, int x2, int y2, boolean isShadow) {
+    paintIcon(c, g, x1, y1, x2, y2);
+  }
+
+  protected void paintIcon(Component c, Graphics g, int x1, int y1, int x2, int y2) {
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/ArrowIcon.java b/src/net/infonode/gui/icon/button/ArrowIcon.java
new file mode 100644
index 0000000..4786ae0
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/ArrowIcon.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ArrowIcon.java,v 1.8 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+public class ArrowIcon extends AbstractButtonIcon {
+  private Direction direction;
+
+  public ArrowIcon(Direction direction) {
+    this.direction = direction;
+  }
+
+  public ArrowIcon(Color color, Direction direction) {
+    super(color);
+    this.direction = direction;
+  }
+
+  public ArrowIcon(Color color, int size, Direction direction) {
+    super(color, size);
+    this.direction = direction;
+  }
+
+  public ArrowIcon(int size, Direction direction) {
+    this(size, direction, true);
+  }
+
+  public ArrowIcon(int size, Direction direction, boolean enabled) {
+    super(size, enabled);
+    this.direction = direction;
+  }
+
+  public Direction getDirection() {
+    return direction;
+  }
+
+  protected void paintIcon(Component c, Graphics g, int x1, int y1, int x2, int y2) {
+    int size = (x2 - x1 + 1) + ((x2 - x1 + 1) % 2) - 1;
+    int offset = (direction.isHorizontal() ? x1 : y1) +
+                 (direction == Direction.RIGHT || direction == Direction.DOWN ?
+                  (size + 1) / 4 : (size - (size + 1) / 2) / 2);
+    int o2 = direction.isHorizontal() ? y1 : x1;
+    int[] c1 = direction == Direction.DOWN || direction == Direction.RIGHT ?
+               new int[]{offset, offset, offset + size / 2 + 1} :
+               new int[]{offset + size / 2 + 1, offset + size / 2 + 1, offset - (direction == Direction.UP ? 1 : 0)};
+    int[] c2 = {o2 + (direction == Direction.DOWN ? 0 : -1),
+                o2 + size + (direction == Direction.UP ? 1 : 0),
+                o2 + size / 2};
+    g.fillPolygon(direction.isHorizontal() ? c1 : c2, direction.isHorizontal() ? c2 : c1, 3);
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/BorderIcon.java b/src/net/infonode/gui/icon/button/BorderIcon.java
new file mode 100644
index 0000000..d52765d
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/BorderIcon.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BorderIcon.java,v 1.6 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class BorderIcon implements Icon {
+  private Icon icon;
+  private Color color;
+  private Insets insets;
+
+  public BorderIcon(Icon icon, int borderSize) {
+    this(icon, null, new Insets(borderSize, borderSize, borderSize, borderSize));
+  }
+
+  public BorderIcon(Icon icon, Color color, Insets insets) {
+    this.icon = icon;
+    this.color = color;
+    this.insets = insets;
+  }
+
+  public void paintIcon(Component c, Graphics g, int x, int y) {
+    if (color != null) {
+      Color oldColor = g.getColor();
+      g.setColor(color);
+      g.fillRect(x, y, getIconWidth(), insets.top);
+      g.fillRect(x, y + getIconHeight() - insets.bottom, getIconWidth(), insets.bottom);
+      g.fillRect(x, y + insets.top, insets.left, getIconHeight() - insets.top - insets.bottom);
+      g.fillRect(x + getIconWidth() - insets.right,
+                 y + insets.top,
+                 insets.right,
+                 getIconHeight() - insets.top - insets.bottom);
+      g.setColor(oldColor);
+    }
+
+    icon.paintIcon(c, g, x + insets.left, y + insets.top);
+  }
+
+  public int getIconWidth() {
+    return insets.left + insets.right + icon.getIconWidth();
+  }
+
+  public int getIconHeight() {
+    return insets.top + insets.bottom + icon.getIconHeight();
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/CloseIcon.java b/src/net/infonode/gui/icon/button/CloseIcon.java
new file mode 100644
index 0000000..78007d9
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/CloseIcon.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: CloseIcon.java,v 1.10 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import net.infonode.gui.GraphicsUtil;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ */
+public class CloseIcon extends AbstractButtonIcon {
+  private static final long serialVersionUID = 1423301116958557861L;
+
+  public CloseIcon() {
+    super();
+  }
+
+  public CloseIcon(Color c) {
+    super(c);
+  }
+
+  public CloseIcon(Color c, int size) {
+    super(c, size);
+  }
+
+  public CloseIcon(int size) {
+    super(size);
+  }
+
+  protected void paintIcon(Component c, Graphics g, int x1, int y1, int x2, int y2) {
+    GraphicsUtil.drawOptimizedLine(g, x1, y1 + 1, x2 - 1, y2);
+    GraphicsUtil.drawOptimizedLine(g, x1 + 1, y1 + 1, x2, y2);
+
+    GraphicsUtil.drawOptimizedLine(g, x1 + 1, y2, x2, y1 + 1);
+    GraphicsUtil.drawOptimizedLine(g, x1, y2, x2 - 1, y1 + 1);
+
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/DockIcon.java b/src/net/infonode/gui/icon/button/DockIcon.java
new file mode 100644
index 0000000..1dc182e
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/DockIcon.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DockIcon.java,v 1.5 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import net.infonode.gui.GraphicsUtil;
+
+import java.awt.*;
+
+/**
+ * @author johan
+ */
+public class DockIcon extends AbstractButtonIcon {
+  private static final long serialVersionUID = 1;
+
+  public DockIcon() {
+    super();
+  }
+
+  public DockIcon(Color c) {
+    super(c);
+  }
+
+  public DockIcon(Color c, int size) {
+    super(c, size);
+  }
+
+  public DockIcon(int size) {
+    super(size);
+  }
+
+  protected void paintIcon(Component c, final Graphics g, final int x1, final int y1, final int x2, final int y2) {
+    int xOffs = (x2 - x1) > 6 ? 1 : 0;
+    int yOffs = xOffs;
+
+    // Bottom left
+    GraphicsUtil.drawOptimizedLine(g, x1 + xOffs, y2 - yOffs, x1 + xOffs, y2 - yOffs - 3);
+    GraphicsUtil.drawOptimizedLine(g, x1 + xOffs, y2 - yOffs, x1 + xOffs + 3, y2 - yOffs);
+
+    // Top right
+    GraphicsUtil.drawOptimizedLine(g, x2 - xOffs - 1, y1 + yOffs, x2 - xOffs - 1, y1 + yOffs + 2);
+    GraphicsUtil.drawOptimizedLine(g, x2 - xOffs - 2, y1 + yOffs - 1, x2 - xOffs - 2, y1 + yOffs - 1);
+
+    // Lines
+    GraphicsUtil.drawOptimizedLine(g, x2 - xOffs - 1, y1 + yOffs, x1 + xOffs, y2 - yOffs - 1);
+    GraphicsUtil.drawOptimizedLine(g, x2 - xOffs - 1, y1 + yOffs + 1, x1 + xOffs, y2 - yOffs);
+    GraphicsUtil.drawOptimizedLine(g, x2 - xOffs - 1, y1 + yOffs + 2, x1 + xOffs + 1, y2 - yOffs);
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/DropDownIcon.java b/src/net/infonode/gui/icon/button/DropDownIcon.java
new file mode 100644
index 0000000..d36aa53
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/DropDownIcon.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DropDownIcon.java,v 1.6 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * @author johan
+ */
+public class DropDownIcon extends ArrowIcon {
+  public DropDownIcon(int size, Direction direction) {
+    this(null, size, direction);
+  }
+
+  public DropDownIcon(Color color, int size, Direction direction) {
+    super(color, size, direction);
+  }
+
+  protected void paintIcon(Component c, Graphics g, int x1, int y1, int x2, int y2) {
+    if (getDirection() == Direction.DOWN) {
+      int offset = (getIconWidth() / 4);
+      g.fillRect(x1, y1, x2 - x1 + 1, 2);
+      super.paintIcon(c, g, x1, y1 + offset, x2, y2 + offset);
+    }
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/MaximizeIcon.java b/src/net/infonode/gui/icon/button/MaximizeIcon.java
new file mode 100644
index 0000000..2a434c8
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/MaximizeIcon.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: MaximizeIcon.java,v 1.10 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import net.infonode.gui.GraphicsUtil;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ */
+public class MaximizeIcon extends AbstractButtonIcon {
+  private static final long serialVersionUID = -5926578998259734919L;
+
+  public MaximizeIcon() {
+    super();
+  }
+
+  public MaximizeIcon(Color color) {
+    super(color);
+  }
+
+  public MaximizeIcon(Color color, int size) {
+    super(color, size);
+  }
+
+  public MaximizeIcon(int size) {
+    super(size);
+  }
+
+  protected void paintIcon(Component c, Graphics g, int x1, int y1, int x2, int y2) {
+    GraphicsUtil.drawOptimizedLine(g, x1, y1, x2, y1);
+    GraphicsUtil.drawOptimizedLine(g, x1, y1 + 1, x2, y1 + 1);
+    GraphicsUtil.drawOptimizedLine(g, x1, y1 + 2, x1, y2);
+    GraphicsUtil.drawOptimizedLine(g, x2, y1 + 2, x2, y2);
+    GraphicsUtil.drawOptimizedLine(g, x1 + 1, y2, x2 - 1, y2);
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/MinimizeIcon.java b/src/net/infonode/gui/icon/button/MinimizeIcon.java
new file mode 100644
index 0000000..00c6494
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/MinimizeIcon.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: MinimizeIcon.java,v 1.10 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import net.infonode.gui.GraphicsUtil;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ */
+public class MinimizeIcon extends AbstractButtonIcon {
+  private static final long serialVersionUID = 6993801965272908275L;
+
+  public MinimizeIcon() {
+    super();
+  }
+
+  public MinimizeIcon(Color color) {
+    super(color);
+  }
+
+  public MinimizeIcon(Color color, int size) {
+    super(color, size);
+  }
+
+  public MinimizeIcon(int size) {
+    super(size);
+  }
+
+  protected void paintIcon(Component c, Graphics g, int x1, int y1, int x2, int y2) {
+    GraphicsUtil.drawOptimizedLine(g, x1, y2 - 1, x2, y2 - 1);
+    GraphicsUtil.drawOptimizedLine(g, x1, y2, x2, y2);
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/RestoreIcon.java b/src/net/infonode/gui/icon/button/RestoreIcon.java
new file mode 100644
index 0000000..77ec4a4
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/RestoreIcon.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: RestoreIcon.java,v 1.10 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import net.infonode.gui.GraphicsUtil;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ */
+public class RestoreIcon extends AbstractButtonIcon {
+  private static final long serialVersionUID = 4019344427358669254L;
+
+  public RestoreIcon() {
+    super();
+  }
+
+  public RestoreIcon(Color color) {
+    super(color);
+  }
+
+  public RestoreIcon(Color color, int size) {
+    super(color, size);
+  }
+
+  public RestoreIcon(int size) {
+    super(size);
+  }
+
+  protected void paintIcon(Component c, Graphics g, int x1, int y1, int x2, int y2) {
+    int boxHeight = (2 * (y2 - y1 + 1)) / 3 - 1;
+    int boxWidth = (5 * (x2 - x1 + 1)) / 6 - 1;
+
+    // Upper box
+    GraphicsUtil.drawOptimizedLine(g, x2 - boxWidth, y1, x2, y1);
+    GraphicsUtil.drawOptimizedLine(g, x2, y1, x2, y1 + boxHeight);
+    GraphicsUtil.drawOptimizedLine(g, x2 - boxWidth, y1 + 1, x2 - boxWidth, y2 - boxHeight);
+    GraphicsUtil.drawOptimizedLine(g, x1 + boxWidth + 1, y1 + boxHeight, x2 - 1, y1 + boxHeight);
+
+    // Lower box
+    GraphicsUtil.drawOptimizedLine(g, x1, y2 - boxHeight, x1, y2);
+    GraphicsUtil.drawOptimizedLine(g, x1 + 1, y2, x1 + boxWidth, y2);
+    GraphicsUtil.drawOptimizedLine(g, x1 + boxWidth, y2 - 1, x1 + boxWidth, y2 - boxHeight + 1);
+    GraphicsUtil.drawOptimizedLine(g, x1 + 1, y2 - boxHeight, x1 + boxWidth, y2 - boxHeight);
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/TreeIcon.java b/src/net/infonode/gui/icon/button/TreeIcon.java
new file mode 100644
index 0000000..8657602
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/TreeIcon.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TreeIcon.java,v 1.6 2005/12/04 13:46:03 jesper Exp $
+
+package net.infonode.gui.icon.button;
+
+import net.infonode.gui.GraphicsUtil;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author Jesper Nordenberg
+ * @version $Revision: 1.6 $ $Date: 2005/12/04 13:46:03 $
+ */
+public class TreeIcon implements Icon {
+  public static final int PLUS = 1;
+  public static final int MINUS = 2;
+
+  private int type;
+  private int width;
+  private int height;
+  private Color color;
+  private Color bgColor;
+  private boolean border = true;
+
+  public TreeIcon(int type, int width, int height, boolean border, Color color, Color bgColor) {
+    this.type = type;
+    this.width = width;
+    this.height = height;
+    this.border = border;
+    this.color = color;
+    this.bgColor = bgColor;
+  }
+
+  public TreeIcon(int type, int width, int height) {
+    this(type, width, height, true, Color.BLACK, null);
+  }
+
+  public void paintIcon(Component c, Graphics g, int x, int y) {
+    if (bgColor != null) {
+      g.setColor(bgColor);
+      g.fillRect(x + 1, y + 1, width - 2, height - 2);
+    }
+
+    g.setColor(color);
+
+    if (border) {
+      g.drawRect(x + 1, y + 1, width - 2, height - 2);
+    }
+
+    GraphicsUtil.drawOptimizedLine(g, x + 3, y + height / 2, x + width - 3, y + height / 2);
+
+    if (type == PLUS)
+      GraphicsUtil.drawOptimizedLine(g, x + width / 2, y + 3, x + width / 2, y + height - 3);
+  }
+
+  public int getIconWidth() {
+    return width;
+  }
+
+  public int getIconHeight() {
+    return height;
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/UndockIcon.java b/src/net/infonode/gui/icon/button/UndockIcon.java
new file mode 100644
index 0000000..a4da60f
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/UndockIcon.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: UndockIcon.java,v 1.7 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import net.infonode.gui.GraphicsUtil;
+
+import java.awt.*;
+
+/**
+ * @author johan
+ */
+public class UndockIcon extends AbstractButtonIcon {
+  private static final long serialVersionUID = 1;
+
+  public UndockIcon() {
+    super();
+  }
+
+  public UndockIcon(Color c) {
+    super(c);
+  }
+
+  public UndockIcon(Color c, int size) {
+    super(c, size);
+  }
+
+  public UndockIcon(int size) {
+    super(size);
+  }
+
+  protected void paintIcon(Component c, final Graphics g, final int x1, final int y1, final int x2, final int y2) {
+    int xOffs = (x2 - x1) > 6 ? 1 : 0;
+    int yOffs = xOffs;
+
+    // Top right
+    GraphicsUtil.drawOptimizedLine(g, x2 - xOffs - 3, y1 + yOffs, x2 - xOffs, y1 + yOffs);
+    GraphicsUtil.drawOptimizedLine(g, x2 - xOffs, y1 + yOffs, x2 - xOffs, y1 + yOffs + 3);
+
+    // Bottom left
+    GraphicsUtil.drawOptimizedLine(g, x1 + xOffs + 1, y2 - yOffs - 2, x1 + xOffs + 1, y2 - yOffs);
+    GraphicsUtil.drawOptimizedLine(g, x1 + xOffs + 2, y2 - yOffs + 1, x1 + xOffs + 2, y2 - yOffs + 1);
+
+    // Lines
+    GraphicsUtil.drawOptimizedLine(g, x1 + xOffs + 1, y2 - yOffs - 2, x2 - xOffs - 1, y1 + yOffs);
+    GraphicsUtil.drawOptimizedLine(g, x1 + xOffs + 1, y2 - yOffs - 1, x2 - xOffs, y1 + yOffs);
+    GraphicsUtil.drawOptimizedLine(g, x1 + xOffs + 1, y2 - yOffs, x2 - xOffs, y1 + yOffs + 1);
+  }
+}
diff --git a/src/net/infonode/gui/icon/button/WindowIcon.java b/src/net/infonode/gui/icon/button/WindowIcon.java
new file mode 100644
index 0000000..a174089
--- /dev/null
+++ b/src/net/infonode/gui/icon/button/WindowIcon.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: WindowIcon.java,v 1.4 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.icon.button;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class WindowIcon extends AbstractButtonIcon {
+  public WindowIcon() {
+  }
+
+  public WindowIcon(Color color) {
+    super(color);
+  }
+
+  public WindowIcon(Color color, int size) {
+    super(color, size);
+  }
+
+  public WindowIcon(int size) {
+    super(size);
+  }
+
+  protected void paintIcon(Component c, Graphics g, int x1, int y1, int x2, int y2, boolean isShadow) {
+    g.fillRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
+
+    if (!isShadow)
+      g.setColor(Color.WHITE);
+
+    g.fillRect(x1 + 1, y1 + 2, x2 - x1 - 1, y2 - y1 - 2);
+  }
+}
diff --git a/src/net/infonode/gui/icon/package.html b/src/net/infonode/gui/icon/package.html
new file mode 100644
index 0000000..497a5a4
--- /dev/null
+++ b/src/net/infonode/gui/icon/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Icon classes and interfaces
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/gui/laf/InfoNodeLookAndFeel.java b/src/net/infonode/gui/laf/InfoNodeLookAndFeel.java
new file mode 100644
index 0000000..4f5f4ce
--- /dev/null
+++ b/src/net/infonode/gui/laf/InfoNodeLookAndFeel.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InfoNodeLookAndFeel.java,v 1.22 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.laf;
+
+import net.infonode.gui.icon.button.TreeIcon;
+import net.infonode.gui.laf.ui.SlimComboBoxUI;
+import net.infonode.gui.laf.ui.SlimInternalFrameUI;
+import net.infonode.gui.laf.ui.SlimMenuItemUI;
+import net.infonode.gui.laf.ui.SlimSplitPaneUI;
+import net.infonode.util.ArrayUtil;
+import net.infonode.util.ColorUtil;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.plaf.ColorUIResource;
+import javax.swing.plaf.FontUIResource;
+import javax.swing.plaf.IconUIResource;
+import javax.swing.plaf.UIResource;
+import javax.swing.plaf.metal.DefaultMetalTheme;
+import javax.swing.plaf.metal.MetalBorders;
+import javax.swing.plaf.metal.MetalLookAndFeel;
+import javax.swing.plaf.metal.MetalTheme;
+import java.awt.*;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * A Look and Feel that's based on Metal. It's slimmer and use other colors than the standard Metal Look and Feel.
+ * Under Java 1.5 the currect Metal theme is stored when the InfoNode Look and Feel is applied, and restored when
+ * another Look and Feel is set. Under Java 1.4 or earlier it is not possible to get the current theme and a
+ * DefaultMetalTheme is set instead.
+ * <p>
+ * To set the look and feel use:
+ * <pre>
+ * UIManager.setLookAndFeel(new InfoNodeLookAndFeel());
+ * </pre>
+ * Or, if you want to use a different theme, use:
+ * <pre>
+ * InfoNodeLookAndFeelTheme theme = new InfoNodeLookAndFeelTheme(...);
+ * // Modify the theme colors, fonts etc.
+ * UIManager.setLookAndFeel(new InfoNodeLookAndFeel(theme));
+ * </pre>
+ * Do not modify the theme after it has been used in the look and feel!
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.22 $
+ */
+public class InfoNodeLookAndFeel extends MetalLookAndFeel {
+  public static final UIManager.LookAndFeelInfo LOOK_AND_FEEL_INFO =
+      new UIManager.LookAndFeelInfo("InfoNode", InfoNodeLookAndFeel.class.getName());
+
+  private static MetalTheme oldMetalTheme;
+
+  private transient InfoNodeLookAndFeelTheme theme;
+
+  private transient DefaultMetalTheme defaultTheme = new DefaultMetalTheme() {
+    public ColorUIResource getPrimaryControlHighlight() {
+      return theme.getPrimaryControlHighlightColor();
+    }
+
+    public ColorUIResource getMenuBackground() {
+      return theme.getControlColor();
+    }
+
+    public ColorUIResource getControlHighlight() {
+      return theme.getControlHighlightColor();
+    }
+
+    public ColorUIResource getControl() {
+      return theme.getControlColor();
+    }
+
+    public ColorUIResource getControlShadow() {
+      return theme.getControlShadowColor();
+    }
+
+    public ColorUIResource getControlDarkShadow() {
+      return theme.getControlDarkShadowColor();
+    }
+
+    // Scrollbars, popups etc.
+    public ColorUIResource getPrimaryControl() {
+      return theme.getPrimaryControlColor();
+    }
+
+    public ColorUIResource getPrimaryControlShadow() {
+      return theme.getPrimaryControlShadowColor();
+    }
+
+    public ColorUIResource getPrimaryControlDarkShadow() {
+      return theme.getPrimaryControlDarkShadowColor();
+    }
+    // End scrollbars
+
+    public ColorUIResource getTextHighlightColor() {
+      return theme.getSelectedTextBackgroundColor();
+    }
+
+    public ColorUIResource getMenuSelectedBackground() {
+      return theme.getSelectedMenuBackgroundColor();
+    }
+
+    public ColorUIResource getWindowBackground() {
+      return theme.getBackgroundColor();
+    }
+
+    protected ColorUIResource getWhite() {
+      return theme.getBackgroundColor();
+    }
+
+    public ColorUIResource getDesktopColor() {
+      return theme.getDesktopColor();
+    }
+
+    public ColorUIResource getHighlightedTextColor() {
+      return theme.getSelectedTextColor();
+    }
+
+    protected ColorUIResource getBlack() {
+      return theme.getTextColor();
+    }
+
+    public ColorUIResource getMenuForeground() {
+      return theme.getTextColor();
+    }
+
+    public ColorUIResource getMenuSelectedForeground() {
+      return theme.getSelectedMenuForegroundColor();
+    }
+
+    public ColorUIResource getFocusColor() {
+      return theme.getFocusColor();
+    }
+
+    public ColorUIResource getControlDisabled() {
+      return theme.getControlColor();
+    }
+
+    public ColorUIResource getSystemTextColor() {
+      return theme.getTextColor();
+    }
+
+    public ColorUIResource getControlTextColor() {
+      return theme.getTextColor();
+    }
+
+    public ColorUIResource getInactiveControlTextColor() {
+      return theme.getInactiveTextColor();
+    }
+
+    public ColorUIResource getInactiveSystemTextColor() {
+      return theme.getInactiveTextColor();
+    }
+
+    public ColorUIResource getUserTextColor() {
+      return theme.getTextColor();
+    }
+
+
+    // --------------- Fonts --------------------------
+
+    public FontUIResource getControlTextFont() {
+      return getSystemTextFont();
+    }
+
+    public FontUIResource getSystemTextFont() {
+      return theme.getFont();
+    }
+
+    public FontUIResource getUserTextFont() {
+      return getSystemTextFont();
+    }
+
+    public FontUIResource getMenuTextFont() {
+      return getSystemTextFont();
+    }
+
+    public FontUIResource getWindowTitleFont() {
+      return getSystemTextFont();
+    }
+
+    public FontUIResource getSubTextFont() {
+      return getSystemTextFont();
+    }
+
+  };
+
+  /**
+   * Constructor.
+   */
+  public InfoNodeLookAndFeel() {
+    this(new InfoNodeLookAndFeelTheme());
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param theme the theme to use. Do not modify the theme after this constructor has been called!
+   */
+  public InfoNodeLookAndFeel(InfoNodeLookAndFeelTheme theme) {
+    this.theme = theme;
+  }
+
+  /**
+   * Gets the active theme
+   *
+   * @return the active theme
+   */
+  public InfoNodeLookAndFeelTheme getTheme() {
+    return theme;
+  }
+
+  public void initialize() {
+    super.initialize();
+
+    if (oldMetalTheme == null) {
+      // Try to obtain the old Metal theme if possible
+      try {
+        oldMetalTheme = (MetalTheme) MetalLookAndFeel.class.getMethod("getCurrentTheme", null).invoke(null, null);
+      }
+      catch (NoSuchMethodException e) {
+        // Ignore
+      }
+      catch (IllegalAccessException e) {
+        // Ignore
+      }
+      catch (InvocationTargetException e) {
+        // Ignore
+      }
+    }
+
+    setCurrentTheme(defaultTheme);
+  }
+
+  public void uninitialize() {
+    setCurrentTheme(oldMetalTheme == null ? new DefaultMetalTheme() : oldMetalTheme);
+    oldMetalTheme = null;
+  }
+
+  public String getName() {
+    return LOOK_AND_FEEL_INFO.getName();
+  }
+
+  public String getDescription() {
+    return "A slim look and feel based on Metal.";
+  }
+
+  protected void initClassDefaults(UIDefaults table) {
+    super.initClassDefaults(table);
+
+    try {
+      {
+        Class cl = SlimSplitPaneUI.class;
+        table.put("SplitPaneUI", cl.getName());
+        table.put(cl.getName(), cl);
+      }
+
+      {
+        Class cl = SlimInternalFrameUI.class;
+        table.put("InternalFrameUI", cl.getName());
+        table.put(cl.getName(), cl);
+      }
+
+      {
+        Class cl = SlimComboBoxUI.class;
+        SlimComboBoxUI.NORMAL_BORDER = theme.getListItemBorder();
+        SlimComboBoxUI.FOCUS_BORDER = theme.getListFocusedItemBorder();
+        table.put("ComboBoxUI", cl.getName());
+        table.put(cl.getName(), cl);
+      }
+
+      {
+        Class cl = SlimMenuItemUI.class;
+        table.put("MenuItemUI", cl.getName());
+        table.put(cl.getName(), cl);
+      }
+    }
+    catch (Exception ex) {
+      throw new RuntimeException(ex);
+    }
+  }
+
+  private static class MyListCellRenderer extends DefaultListCellRenderer {
+    private Border normalBorder;
+    private Border focusBorder;
+
+    MyListCellRenderer(Border normalBorder, Border focusBorder) {
+      this.normalBorder = normalBorder;
+      this.focusBorder = focusBorder;
+    }
+
+    public Component getListCellRendererComponent(JList list, Object value, int index,
+                                                  boolean isSelected,
+                                                  boolean cellHasFocus) {
+      JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+      label.setBorder(cellHasFocus ? focusBorder : normalBorder);
+      return label;
+    }
+
+    public static class UIResource extends MyListCellRenderer implements javax.swing.plaf.UIResource {
+      public UIResource(Border normalBorder, Border focusBorder) {
+        super(normalBorder, focusBorder);
+      }
+    }
+  }
+
+  protected void initComponentDefaults(UIDefaults table) {
+    super.initComponentDefaults(table);
+
+    Class iconClass = MetalLookAndFeel.class;
+    UIResource menuItemBorder = new MetalBorders.MenuItemBorder() {
+      public Insets getBorderInsets(Component c) {
+        return new Insets(2, 0, 2, 0);
+      }
+    };
+
+    Object[] defaults = {
+      "SplitPane.dividerSize", new Integer(theme.getSplitPaneDividerSize()),
+//      "SplitPaneDivider.border", new BorderUIResource(splitPaneDividerBorder),
+//      "SplitPane.border", new BorderUIResource(splitPaneBorder),
+
+//    "TabbedPane.contentBorderInsets", tabbedPaneContentInsets,
+//    "TabbedPane.tabAreaInsets", new InsetsUIResource(0, 0, 0, 0),
+//    "TabbedPane.selectedTabPadInsets", new InsetsUIResource(0, 0, 0, 0),
+//    "TabbedPane.tabInsets", new InsetsUIResource(0, 0, 0, 0),
+      "TabbedPane.background", theme.getControlLightShadowColor(),
+      //"TabbedPane.darkShadow", new Color(160, 160, 160),
+
+      "ComboBox.selectionBackground", theme.getSelectedMenuBackgroundColor(),
+      "ComboBox.selectionForeground", theme.getSelectedMenuForegroundColor(),
+
+      "List.cellRenderer", new UIDefaults.ActiveValue() {
+        public Object createValue(UIDefaults table) {
+          return new MyListCellRenderer.UIResource(theme.getListItemBorder(), theme.getListFocusedItemBorder());
+        }
+      },
+
+      "ToolTip.foreground", theme.getTooltipForegroundColor(),
+      "ToolTip.background", theme.getTooltipBackgroundColor(),
+
+      "Viewport.background", theme.getBackgroundColor(),
+
+      "ScrollBar.background", theme.getScrollBarBackgroundColor(),
+      "ScrollBar.shadow", theme.getScrollBarBackgroundShadowColor(),
+      "ScrollBar.width", new Integer(theme.getScrollBarWidth()),
+//    "ScrollBar.border", new BorderUIResource(new LineBorder(Color.GRAY, 1)),
+
+//    "ScrollBar.thumb", new RelativeColor(thumbColor, 0.8).getColor(),
+//    "ScrollBar.thumbShadow", new RelativeColor(thumbColor, 0.7).getColor(),
+//    "ScrollBar.thumbHighlight", new RelativeColor(thumbColor, 1).getColor(),
+
+/*    "ScrollBar.thumb", new ColorUIResource(255, 255, 255),
+    "ScrollBar.thumbShadow", new ColorUIResource(0, 255, 0),
+    "ScrollBar.thumbHighlight", new ColorUIResource(255, 255, 255),
+*/
+
+      "Table.focusCellBackground", new ColorUIResource(ColorUtil.mult(theme.getSelectedMenuBackgroundColor(), 1.40f)),
+      "Table.focusCellForeground", theme.getSelectedMenuForegroundColor(),
+
+      "TableHeader.cellBorder", theme.getTableHeaderCellBorder(),
+
+      "InternalFrame.activeTitleBackground", theme.getActiveInternalFrameTitleBackgroundColor(),
+      "InternalFrame.activeTitleForeground", theme.getActiveInternalFrameTitleForegroundColor(),
+      "InternalFrame.activeTitleGradient", theme.getActiveInternalFrameTitleGradientColor(), //ColorUtil.mult(theme.getActiveInternalFrameTitleBackgroundColor(), 1.2),
+      "InternalFrame.inactiveTitleBackground", theme.getInactiveInternalFrameTitleBackgroundColor(),
+      "InternalFrame.inactiveTitleForeground", theme.getInactiveInternalFrameTitleForegroundColor(),
+      "InternalFrame.inactiveTitleGradient", theme.getInactiveInternalFrameTitleGradientColor(), //ColorUtil.mult(theme.getInactiveInternalFrameTitleBackgroundColor(), 1.2),
+      "InternalFrame.icon", theme.getInternalFrameIcon(),
+      "InternalFrame.iconifyIcon", theme.getInternalFrameIconifyIcon(),
+      "InternalFrame.minimizeIcon", theme.getInternalFrameMinimizeIcon(),
+      "InternalFrame.maximizeIcon", theme.getInternalFrameMaximizeIcon(),
+      "InternalFrame.closeIcon", theme.getInternalFrameCloseIcon(),
+      "InternalFrame.border", theme.getInternalFrameBorder(),
+      "InternalFrame.titleFont", theme.getInternalFrameTitleFont(),
+
+      "MenuBar.border", theme.getMenuBarBorder(),
+
+      "MenuItem.border", menuItemBorder,
+      "Menu.border", menuItemBorder,
+//      "CheckBoxMenuItem.border", menuItemBorder,
+//      "RadioButtonMenuItem.border", menuItemBorder,
+
+      "Spinner.border", theme.getTextFieldBorder(),
+      "Spinner.background", new ColorUIResource(theme.getBackgroundColor()),
+
+      "PopupMenu.border", theme.getPopupMenuBorder(),
+
+      "TextField.border", theme.getTextFieldBorder(),
+      "FormattedTextField.border", theme.getTextFieldBorder(),
+
+//      "Button.border", new BorderUIResource(buttonBorder),
+//      "Button.disabledShadow", new ColorUIResource(Color.GREEN), //ColorUtil.blend(textColor, controlColor, 0.5f)),
+      "Button.textShiftOffset", new Integer(2),
+      "Button.select", theme.getControlLightShadowColor(),
+//    "Button.focus", focusColor,
+      "Button.margin", theme.getButtonMargin(),
+      "Button.disabledText", theme.getInactiveTextColor(),
+      //"Button.background", buttonBackground.getColor(),
+
+      "ToggleButton.margin", theme.getButtonMargin(),
+      "ToggleButton.select", theme.getControlLightShadowColor(),
+      "ToggleButton.textShiftOffset", new Integer(2),
+
+      "Tree.openIcon", theme.getTreeOpenIcon(),
+      "Tree.closedIcon", theme.getTreeClosedIcon(),
+      "Tree.leafIcon", theme.getTreeLeafIcon(),
+      "Tree.collapsedIcon", new IconUIResource(
+          new TreeIcon(TreeIcon.PLUS, 10, 10, true, theme.getTextColor(), theme.getTreeIconBackgroundColor())),
+      "Tree.expandedIcon", new IconUIResource(
+          new TreeIcon(TreeIcon.MINUS, 10, 10, true, theme.getTextColor(), theme.getTreeIconBackgroundColor())),
+      "Tree.leftChildIndent", new Integer(5),
+      "Tree.rightChildIndent", new Integer(11),
+//    "Tree.rowHeight", new Integer(12),
+
+      "OptionPane.errorIcon", LookAndFeel.makeIcon(iconClass, "icons/Error.gif"),
+      "OptionPane.informationIcon", LookAndFeel.makeIcon(iconClass, "icons/Inform.gif"),
+      "OptionPane.warningIcon", LookAndFeel.makeIcon(iconClass, "icons/Warn.gif"),
+      "OptionPane.questionIcon", LookAndFeel.makeIcon(iconClass, "icons/Question.gif"),
+      "OptionPane.buttonFont", theme.getOptionPaneButtonFont(),
+    };
+
+    table.putDefaults(defaults);
+  }
+
+  /**
+   * Installs this look and feel with the {@link UIManager}, if it's not already installed.
+   */
+  public static void install() {
+    if (!ArrayUtil.contains(UIManager.getInstalledLookAndFeels(), LOOK_AND_FEEL_INFO))
+      UIManager.installLookAndFeel(LOOK_AND_FEEL_INFO);
+  }
+
+}
diff --git a/src/net/infonode/gui/laf/InfoNodeLookAndFeelReleaseInfo.java b/src/net/infonode/gui/laf/InfoNodeLookAndFeelReleaseInfo.java
new file mode 100644
index 0000000..9b2fb5e
--- /dev/null
+++ b/src/net/infonode/gui/laf/InfoNodeLookAndFeelReleaseInfo.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InfoNodeLookAndFeelReleaseInfo.java,v 1.4 2004/09/22 14:35:04 jesper Exp $
+package net.infonode.gui.laf;
+
+import net.infonode.util.AntUtils;
+import net.infonode.util.ReleaseInfo;
+
+/**
+ * InfoNode Look and Feel release information. Contains product name, vendor, build time and
+ * version info for the current release.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class InfoNodeLookAndFeelReleaseInfo {
+  private static ReleaseInfo productInfo =
+      new ReleaseInfo("InfoNode Look and Feel GPL",
+                      "NNL Technology AB",
+                      AntUtils.getBuildTime(1235486816015L),
+                      AntUtils.createProductVersion(1, 6, 1),
+                      "GNU General Public License, Version 2",
+                      "http://www.infonode.net");
+
+  /**
+   * Gets the release information
+   *
+   * @return Release information
+   */
+  public static ReleaseInfo getReleaseInfo() {
+    return productInfo;
+  }
+}
diff --git a/src/net/infonode/gui/laf/InfoNodeLookAndFeelTheme.java b/src/net/infonode/gui/laf/InfoNodeLookAndFeelTheme.java
new file mode 100644
index 0000000..0edea16
--- /dev/null
+++ b/src/net/infonode/gui/laf/InfoNodeLookAndFeelTheme.java
@@ -0,0 +1,1223 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InfoNodeLookAndFeelTheme.java,v 1.16 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.laf;
+
+import net.infonode.gui.Colors;
+import net.infonode.gui.border.EdgeBorder;
+import net.infonode.gui.border.EtchedLineBorder;
+import net.infonode.gui.border.HighlightBorder;
+import net.infonode.gui.border.PopupMenuBorder;
+import net.infonode.gui.icon.EmptyIcon;
+import net.infonode.gui.icon.button.*;
+import net.infonode.gui.laf.value.BorderValue;
+import net.infonode.gui.laf.value.ColorValue;
+import net.infonode.gui.laf.value.FontValue;
+import net.infonode.util.ArrayUtil;
+import net.infonode.util.ColorUtil;
+
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.LineBorder;
+import javax.swing.plaf.*;
+import java.awt.*;
+
+/**
+ * A theme for InfoNode look and feel. The theme infers some default colors from others, so modifying a color might
+ * affect other, unmodified colors.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.16 $
+ */
+public class InfoNodeLookAndFeelTheme {
+  private static final float PRIMARY_HUE = Colors.ROYAL_BLUE_HUE;
+  private static final float PRIMARY_SATURATION = 0.6f;
+  private static final float PRIMARY_BRIGHTNESS = 0.67f;
+
+  public static final Color DEFAULT_CONTROL_COLOR = Color.getHSBColor(Colors.SAND_HUE, 0.058f, 0.89f);
+  public static final Color DEFAULT_PRIMARY_CONTROL_COLOR = Color.getHSBColor(PRIMARY_HUE, PRIMARY_SATURATION, 1f);
+  public static final Color DEFAULT_BACKGROUND_COLOR = new Color(250, 250, 247);
+  public static final Color DEFAULT_TEXT_COLOR = Color.BLACK;
+  public static final Color DEFAULT_SELECTED_BACKGROUND_COLOR = Color.getHSBColor(PRIMARY_HUE,
+                                                                                  PRIMARY_SATURATION + 0.1f,
+                                                                                  PRIMARY_BRIGHTNESS);
+  public static final Color DEFAULT_SELECTED_TEXT_COLOR = Color.WHITE;
+  public static final Color DEFAULT_TOOLTIP_BACKGROUND_COLOR = new Color(255, 255, 180);
+  public static final Color DEFAULT_TOOLTIP_FOREGROUND_COLOR = Color.BLACK;
+  public static final Color DEFAULT_DESKTOP_COLOR = Color.getHSBColor(PRIMARY_HUE - 0.02f,
+                                                                      PRIMARY_SATURATION,
+                                                                      PRIMARY_BRIGHTNESS);
+
+  public static final int DEFAULT_FONT_SIZE = 11;
+
+  private static final String[] FONT_NAMES = {/*"Tahoma", */"Dialog"};
+
+  private FontUIResource font = new FontUIResource("Dialog", 0, 11);
+  private FontUIResource boldFont;
+
+  private ColorValue controlColor = new ColorValue();
+  private ColorValue primaryControlColor = new ColorValue();
+  private ColorValue backgroundColor = new ColorValue();
+  private ColorValue textColor = new ColorValue();
+  private ColorValue selectedTextBackgroundColor = new ColorValue();
+
+  private ColorValue focusColor = new ColorValue();
+  private ColorValue selectedTextColor = new ColorValue();
+  private ColorValue tooltipBackgroundColor = new ColorValue(DEFAULT_TOOLTIP_BACKGROUND_COLOR);
+  private ColorValue tooltipForegroundColor = new ColorValue(DEFAULT_TOOLTIP_FOREGROUND_COLOR);
+  private ColorValue desktopColor = new ColorValue(DEFAULT_DESKTOP_COLOR);
+
+  private ColorValue treeIconBackgroundColor = new ColorValue();
+
+  private ColorValue selectedMenuBackgroundColor = new ColorValue();
+  private ColorValue selectedMenuForegroundColor = new ColorValue();
+
+  private ColorValue inactiveTextColor = new ColorValue();
+
+  private ColorUIResource controlHighlightColor;
+  private ColorUIResource controlLightShadowColor;
+  private ColorUIResource controlShadowColor;
+  private ColorUIResource controlDarkShadowColor;
+
+  private ColorUIResource primaryControlHighlightColor;
+  private ColorUIResource primaryControlShadowColor;
+  private ColorUIResource primaryControlDarkShadowColor;
+
+  private ColorValue scrollBarBackgroundColor = new ColorValue();
+  private ColorUIResource scrollBarBackgroundShadowColor;
+
+  private ColorValue activeInternalFrameTitleBackgroundColor = new ColorValue();
+  private ColorValue activeInternalFrameTitleGradientColor = new ColorValue();
+  private ColorValue activeInternalFrameTitleForegroundColor = new ColorValue();
+  private ColorValue inactiveInternalFrameTitleBackgroundColor = new ColorValue();
+  private ColorValue inactiveInternalFrameTitleGradientColor = new ColorValue();
+  private ColorValue inactiveInternalFrameTitleForegroundColor = new ColorValue();
+  private IconUIResource internalFrameIcon = new IconUIResource(new BorderIcon(new WindowIcon(Color.BLACK, 12), 2));
+  private IconUIResource internalFrameIconifyIcon = new IconUIResource(new MinimizeIcon());
+  private IconUIResource internalFrameMinimizeIcon = new IconUIResource(new RestoreIcon());
+  private IconUIResource internalFrameMaximizeIcon = new IconUIResource(new MaximizeIcon());
+  private IconUIResource internalFrameCloseIcon = new IconUIResource(new CloseIcon());
+  private BorderUIResource internalFrameBorder = new BorderUIResource(new LineBorder(Color.BLACK, 2));
+
+  private FontValue internalFrameTitleFont = new FontValue();
+  private FontValue optionPaneButtonFont = new FontValue();
+
+  private IconUIResource treeOpenIcon = new IconUIResource(EmptyIcon.INSTANCE);
+  private IconUIResource treeClosedIcon = new IconUIResource(EmptyIcon.INSTANCE);
+  private IconUIResource treeLeafIcon = new IconUIResource(EmptyIcon.INSTANCE);
+
+  private BorderValue menuBarBorder = new BorderValue();
+  private BorderValue popupMenuBorder = new BorderValue();
+
+  private BorderValue tableHeaderCellBorder = new BorderValue();
+
+  private BorderValue textFieldBorder = new BorderValue();
+
+  private BorderValue listItemBorder = new BorderValue(new EmptyBorder(1, 4, 1, 4));
+  private BorderValue listFocusedItemBorder = new BorderValue();
+
+  private int splitPaneDividerSize = 7;
+  private int scrollBarWidth = 17;
+  private InsetsUIResource buttonMargin = new InsetsUIResource(1, 6, 1, 6);
+
+  private double shadingFactor = 1.6;
+
+  private String name;
+
+  /**
+   * Creates a default InfoNode look and feel theme.
+   */
+  public InfoNodeLookAndFeelTheme() {
+    this("Default Theme",
+         DEFAULT_CONTROL_COLOR,
+         DEFAULT_PRIMARY_CONTROL_COLOR,
+         DEFAULT_BACKGROUND_COLOR,
+         DEFAULT_TEXT_COLOR,
+         DEFAULT_SELECTED_BACKGROUND_COLOR,
+         DEFAULT_SELECTED_TEXT_COLOR);
+  }
+
+  /**
+   * Creates a theme with custom colors.
+   *
+   * @param name                the name of this theme
+   * @param controlColor        the background color for buttons, labels etc.
+   * @param primaryControlColor the color of scrollbar "knobs", text and menu selection background
+   * @param backgroundColor     the background color for viewports, tree's, tables etc.
+   * @param textColor           the text color
+   */
+  public InfoNodeLookAndFeelTheme(String name,
+                                  Color controlColor,
+                                  Color primaryControlColor,
+                                  Color backgroundColor,
+                                  Color textColor) {
+    this(name,
+         controlColor,
+         primaryControlColor,
+         backgroundColor,
+         textColor,
+         primaryControlColor,
+         ColorUtil.getOpposite(primaryControlColor));
+  }
+
+  /**
+   * Creates a theme with custom colors.
+   *
+   * @param name                    the name of this theme
+   * @param controlColor            the background color for buttons, labels etc.
+   * @param primaryControlColor     the color of scrollbar "knobs"
+   * @param backgroundColor         the background color for viewports, tree's, tables etc.
+   * @param textColor               the text color
+   * @param selectedBackgroundColor the background color for selected text, selected menu items
+   * @param selectedTextColor       the text color for selected text, selected menu items
+   */
+  public InfoNodeLookAndFeelTheme(String name,
+                                  Color controlColor,
+                                  Color primaryControlColor,
+                                  Color backgroundColor,
+                                  Color textColor,
+                                  Color selectedBackgroundColor,
+                                  Color selectedTextColor) {
+    this(name,
+         controlColor,
+         primaryControlColor,
+         backgroundColor,
+         textColor,
+         selectedBackgroundColor,
+         selectedTextColor,
+         1.3);
+  }
+
+  /**
+   * Creates a theme with custom colors.
+   *
+   * @param name                    the name of this theme
+   * @param controlColor            the background color for buttons, labels etc.
+   * @param primaryControlColor     the color of scrollbar "knobs"
+   * @param backgroundColor         the background color for viewports, tree's, tables etc.
+   * @param textColor               the text color
+   * @param selectedBackgroundColor the background color for selected text, selected menu items
+   * @param selectedTextColor       the text color for selected text, selected menu items
+   * @param shadingFactor           the shading factor is used when calculating brighter and darker control colors. A
+   *                                higher factor gives brighter and darker colors.
+   */
+  public InfoNodeLookAndFeelTheme(String name,
+                                  Color controlColor,
+                                  Color primaryControlColor,
+                                  Color backgroundColor,
+                                  Color textColor,
+                                  Color selectedBackgroundColor,
+                                  Color selectedTextColor,
+                                  double shadingFactor) {
+    this.name = name;
+    String[] fontNames = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
+
+    for (int i = 0; i < FONT_NAMES.length; i++) {
+      if (ArrayUtil.containsEqual(fontNames, FONT_NAMES[i])) {
+        font = new FontUIResource(new Font(FONT_NAMES[i], Font.PLAIN, DEFAULT_FONT_SIZE));
+        break;
+      }
+    }
+
+    updateFonts();
+
+    this.controlColor.setColor(controlColor);
+    this.primaryControlColor.setColor(primaryControlColor);
+    this.backgroundColor.setColor(backgroundColor);
+    this.selectedTextBackgroundColor.setColor(selectedBackgroundColor);
+    this.selectedTextColor.setColor(selectedTextColor);
+    this.textColor.setColor(textColor);
+    this.shadingFactor = shadingFactor;
+    updateColors();
+  }
+
+  private void updateFonts() {
+    boldFont = new FontUIResource(font.deriveFont(Font.BOLD));
+    internalFrameTitleFont.setDefaultFont(boldFont);
+    optionPaneButtonFont.setDefaultFont(boldFont);
+  }
+
+  private void updateColors() {
+    focusColor.setDefaultColor(ColorUtil.blend(controlColor.getColor(), textColor.getColor(), 0.5f));
+
+    inactiveTextColor.setDefaultColor(focusColor);
+
+    double invShadeAmount = 1.0 / (1 + shadingFactor * 1.2);
+    double invShadeAmount2 = 1.0 / (1 + shadingFactor / 2);
+    double invShadeAmount3 = 1.0 / (1 + shadingFactor / 7);
+
+    controlHighlightColor = new ColorUIResource(ColorUtil.mult(controlColor.getColor(), 1 + shadingFactor));
+    controlLightShadowColor = new ColorUIResource(ColorUtil.mult(controlColor.getColor(), invShadeAmount3));
+    controlShadowColor = new ColorUIResource(ColorUtil.mult(controlColor.getColor(), invShadeAmount2));
+    controlDarkShadowColor = new ColorUIResource(ColorUtil.mult(controlColor.getColor(), invShadeAmount));
+
+    primaryControlHighlightColor = controlHighlightColor;
+    primaryControlShadowColor = new ColorUIResource(ColorUtil.mult(primaryControlColor.getColor(), invShadeAmount2));
+    primaryControlDarkShadowColor =
+    new ColorUIResource(ColorUtil.mult(primaryControlColor.getColor(), invShadeAmount));
+
+    scrollBarBackgroundColor.setDefaultColor(controlLightShadowColor);
+    scrollBarBackgroundShadowColor = new ColorUIResource(ColorUtil.mult(scrollBarBackgroundColor.getColor(),
+                                                                        invShadeAmount));
+
+    selectedMenuBackgroundColor.setDefaultColor(selectedTextBackgroundColor);
+    selectedMenuForegroundColor.setDefaultColor(selectedTextColor);
+
+    treeIconBackgroundColor.setDefaultColor(
+        ColorUtil.blend(backgroundColor.getColor(), primaryControlColor.getColor(), 0.15f));
+
+    activeInternalFrameTitleBackgroundColor.setDefaultColor(ColorUtil.blend(primaryControlColor.getColor(),
+                                                                            ColorUtil.getOpposite(getTextColor()),
+                                                                            0.5f));
+    activeInternalFrameTitleForegroundColor.setDefaultColor(getTextColor());
+    activeInternalFrameTitleGradientColor.setDefaultColor(
+        ColorUtil.mult(activeInternalFrameTitleBackgroundColor.getColor(), 1.2));
+    inactiveInternalFrameTitleBackgroundColor.setDefaultColor(controlLightShadowColor);
+    inactiveInternalFrameTitleForegroundColor.setDefaultColor(getTextColor());
+    inactiveInternalFrameTitleGradientColor.setDefaultColor(
+        ColorUtil.mult(inactiveInternalFrameTitleBackgroundColor.getColor(), 1.2));
+
+    menuBarBorder.setDefaultBorder(new BorderUIResource(
+        new EtchedLineBorder(false, false, true, false, controlHighlightColor, controlDarkShadowColor)));
+    popupMenuBorder.setDefaultBorder(
+        new BorderUIResource(new PopupMenuBorder(controlHighlightColor, controlDarkShadowColor)));
+    textFieldBorder.setDefaultBorder(
+        new BorderUIResource(new CompoundBorder(new LineBorder(controlDarkShadowColor), new EmptyBorder(1, 2, 1, 2))));
+
+    tableHeaderCellBorder.setDefaultBorder(new BorderUIResource(new CompoundBorder(new CompoundBorder(
+        new EdgeBorder(controlDarkShadowColor, false, true, false, true),
+        new HighlightBorder(false, controlHighlightColor)),
+                                                                                   new EmptyBorder(1, 4, 1, 4))));
+
+    listFocusedItemBorder.setDefaultBorder(new CompoundBorder(new LineBorder(focusColor.getColor()),
+                                                              new EmptyBorder(0, 3, 0, 3)));
+  }
+
+  /**
+   * Returns the theme name.
+   *
+   * @return the theme name
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Returns the shading factor. The shading factor is used when calculating brighter and darker control colors. A
+   * higher factor gives brighter and darker colors.
+   *
+   * @return the shading factor
+   */
+  public double getShadingFactor() {
+    return shadingFactor;
+  }
+
+  /**
+   * Sets the shading factor. The shading factor is used when calculating brighter and darker control colors. A higher
+   * factor gives brighter and darker colors.
+   *
+   * @param shadingFactor the shading factor
+   */
+  public void setShadingFactor(double shadingFactor) {
+    this.shadingFactor = shadingFactor;
+    updateColors();
+  }
+
+  /**
+   * Returns the base font. This font is used as default font for all text.
+   *
+   * @return returns the base font
+   */
+  public FontUIResource getFont() {
+    return font;
+  }
+
+  /**
+   * Sets the base font. This font is used as default font for all text.
+   *
+   * @param font the base font
+   */
+  public void setFont(FontUIResource font) {
+    this.font = font;
+    updateFonts();
+  }
+
+  /**
+   * Gets the background color used for {@link javax.swing.JComponent}.
+   *
+   * @return the background color used for {@link javax.swing.JComponent}
+   */
+  public ColorUIResource getControlColor() {
+    return controlColor.getColor();
+  }
+
+  /**
+   * Gets the color of scrollbar "knobs" etc.
+   *
+   * @return the color of scrollbar "knobs" etc,
+   */
+  public ColorUIResource getPrimaryControlColor() {
+    return primaryControlColor.getColor();
+  }
+
+  /**
+   * Gets the background color for {@link javax.swing.JViewport}, {@link javax.swing.JTree}, {@link javax.swing.JTable}
+   * etc.
+   *
+   * @return the background color for {@link javax.swing.JViewport}, {@link javax.swing.JTree}, {@link
+   *         javax.swing.JTable} etc.
+   */
+  public ColorUIResource getBackgroundColor() {
+    return backgroundColor.getColor();
+  }
+
+  /**
+   * Gets the text color.
+   *
+   * @return the text color
+   */
+  public ColorUIResource getTextColor() {
+    return textColor.getColor();
+  }
+
+  /**
+   * Gets the selected text background color.
+   *
+   * @return the selected text background color
+   */
+  public ColorUIResource getSelectedTextBackgroundColor() {
+    return selectedTextBackgroundColor.getColor();
+  }
+
+  /**
+   * Gets the control focus marker color.
+   *
+   * @return the control focus marker color
+   */
+  public ColorUIResource getFocusColor() {
+    return focusColor.getColor();
+  }
+
+  /**
+   * Gets the selected text color.
+   *
+   * @return the selected text color
+   */
+  public ColorUIResource getSelectedTextColor() {
+    return selectedTextColor.getColor();
+  }
+
+  /**
+   * Gets the background color for {@link javax.swing.JToolTip}.
+   *
+   * @return the background color for {@link javax.swing.JToolTip}
+   */
+  public ColorUIResource getTooltipBackgroundColor() {
+    return tooltipBackgroundColor.getColor();
+  }
+
+  /**
+   * Gets the desktop color used in {@link javax.swing.JDesktopPane} etc.
+   *
+   * @return the desktop color used in {@link javax.swing.JDesktopPane} etc.
+   */
+  public ColorUIResource getDesktopColor() {
+    return desktopColor.getColor();
+  }
+
+  /**
+   * Gets the background color used for collapse and expand icons in a {@link javax.swing.JTree}.
+   *
+   * @return the background color used for collapse and expand icons in a {@link javax.swing.JTree}
+   */
+  public ColorUIResource getTreeIconBackgroundColor() {
+    return treeIconBackgroundColor.getColor();
+  }
+
+  /**
+   * Gets the background color used for selected {@link javax.swing.JMenuItem}'s.
+   *
+   * @return the background color used for selected {@link javax.swing.JMenuItem}'s
+   */
+  public ColorUIResource getSelectedMenuBackgroundColor() {
+    return selectedMenuBackgroundColor.getColor();
+  }
+
+  /**
+   * Gets the foreground color used for selected {@link javax.swing.JMenuItem}'s.
+   *
+   * @return the foreground color used for selected {@link javax.swing.JMenuItem}'s
+   */
+  public ColorUIResource getSelectedMenuForegroundColor() {
+    return selectedMenuForegroundColor.getColor();
+  }
+
+  /**
+   * Gets the color used for inactive text.
+   *
+   * @return the color used for inactive text
+   */
+  public ColorUIResource getInactiveTextColor() {
+    return inactiveTextColor.getColor();
+  }
+
+  /**
+   * Gets the control highlight color. By default this is a color a little brighter than the control color.
+   *
+   * @return the control highlight color
+   */
+  public ColorUIResource getControlHighlightColor() {
+    return controlHighlightColor;
+  }
+
+  /**
+   * Gets the control light shadow color. By default this is a color a little darker than the control color.
+   *
+   * @return the control light shadow color
+   */
+  public ColorUIResource getControlLightShadowColor() {
+    return controlLightShadowColor;
+  }
+
+  /**
+   * Gets the control shadow color. By default this is a color a little darker than the control light shadow color.
+   *
+   * @return the control shadow color
+   */
+  public ColorUIResource getControlShadowColor() {
+    return controlShadowColor;
+  }
+
+  /**
+   * Gets the control dark shadow color. By default this is a color a little darker than the control shadow color.
+   *
+   * @return the control dark shadow color
+   */
+  public ColorUIResource getControlDarkShadowColor() {
+    return controlDarkShadowColor;
+  }
+
+  /**
+   * Gets the primary control highlight color. By default this color is the same as the control highlight color..
+   *
+   * @return the primary control highlight color
+   */
+  public ColorUIResource getPrimaryControlHighlightColor() {
+    return primaryControlHighlightColor;
+  }
+
+  /**
+   * Gets the primary control shadow color. By default this is a color a little darker than the primary control color.
+   *
+   * @return the primary control shadow color
+   */
+  public ColorUIResource getPrimaryControlShadowColor() {
+    return primaryControlShadowColor;
+  }
+
+  /**
+   * Gets the primary control dark shadow color. By default this is a color a little darker than the primary control
+   * shadow color.
+   *
+   * @return the primary control dark shadow color
+   */
+  public ColorUIResource getPrimaryControlDarkShadowColor() {
+    return primaryControlDarkShadowColor;
+  }
+
+  /**
+   * Gets the background color for {@link javax.swing.JScrollBar}'s.
+   *
+   * @return the background color for {@link javax.swing.JScrollBar}'s
+   */
+  public ColorUIResource getScrollBarBackgroundColor() {
+    return scrollBarBackgroundColor.getColor();
+  }
+
+  /**
+   * Gets the background shadow color for {@link javax.swing.JScrollBar}'s. By default this is a color a little darker
+   * than the scroll bar background color.
+   *
+   * @return the background color for {@link javax.swing.JScrollBar}'s.
+   */
+  public ColorUIResource getScrollBarBackgroundShadowColor() {
+    return scrollBarBackgroundShadowColor;
+  }
+
+  /**
+   * Gets the background color for active {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the background color for active {@link javax.swing.JInternalFrame}'s
+   */
+  public ColorUIResource getActiveInternalFrameTitleBackgroundColor() {
+    return activeInternalFrameTitleBackgroundColor.getColor();
+  }
+
+  /**
+   * Gets the foreground color for active {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the foreground color for active {@link javax.swing.JInternalFrame}'s
+   */
+  public ColorUIResource getActiveInternalFrameTitleForegroundColor() {
+    return activeInternalFrameTitleForegroundColor.getColor();
+  }
+
+  /**
+   * Gets the gradient color for active {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the gradient color for active {@link javax.swing.JInternalFrame}'s
+   */
+  public ColorUIResource getActiveInternalFrameTitleGradientColor() {
+    return activeInternalFrameTitleGradientColor.getColor();
+  }
+
+  /**
+   * Gets the background color for inactive {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the background color for inactive {@link javax.swing.JInternalFrame}'s
+   */
+  public ColorUIResource getInactiveInternalFrameTitleBackgroundColor() {
+    return inactiveInternalFrameTitleBackgroundColor.getColor();
+  }
+
+  /**
+   * Gets the foreground color for inactive {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the foreground color for inactive {@link javax.swing.JInternalFrame}'s
+   */
+  public ColorUIResource getInactiveInternalFrameTitleForegroundColor() {
+    return inactiveInternalFrameTitleForegroundColor.getColor();
+  }
+
+  /**
+   * Gets the gradient color for inactive {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the gradient color for inactive {@link javax.swing.JInternalFrame}'s
+   */
+  public ColorUIResource getInactiveInternalFrameTitleGradientColor() {
+    return inactiveInternalFrameTitleGradientColor.getColor();
+  }
+
+  /**
+   * Gets the border around cells in {@link javax.swing.table.JTableHeader}'s.
+   *
+   * @return the border around cells in {@link javax.swing.table.JTableHeader}'s
+   */
+  public BorderUIResource getTableHeaderCellBorder() {
+    return tableHeaderCellBorder.getBorder();
+  }
+
+  /**
+   * Gets the icon to the left in the title bar of {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the icon to the left in the title bar of {@link javax.swing.JInternalFrame}'s
+   */
+  public IconUIResource getInternalFrameIcon() {
+    return internalFrameIcon;
+  }
+
+  /**
+   * Sets the icon to the left in the title bar of {@link javax.swing.JInternalFrame}'s.
+   *
+   * @param internalFrameIcon the icon
+   */
+  public void setInternalFrameIcon(IconUIResource internalFrameIcon) {
+    this.internalFrameIcon = internalFrameIcon;
+  }
+
+  /**
+   * Gets the icon used in the minimize button in the title bar of {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the icon used in the minimize button in the title bar of {@link javax.swing.JInternalFrame}'s
+   */
+  public IconUIResource getInternalFrameMinimizeIcon() {
+    return internalFrameMinimizeIcon;
+  }
+
+  /**
+   * Sets the icon used in the minimize button in the title bar of {@link javax.swing.JInternalFrame}'s.
+   *
+   * @param internalFrameMinimizeIcon the icon
+   */
+  public void setInternalFrameMinimizeIcon(IconUIResource internalFrameMinimizeIcon) {
+    this.internalFrameMinimizeIcon = internalFrameMinimizeIcon;
+  }
+
+  /**
+   * Gets the icon used in the maximize button in the title bar of {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the icon used in the minimize button in the title bar of {@link javax.swing.JInternalFrame}'s
+   */
+  public IconUIResource getInternalFrameMaximizeIcon() {
+    return internalFrameMaximizeIcon;
+  }
+
+  /**
+   * Sets the icon used in the maximize button in the title bar of {@link javax.swing.JInternalFrame}'s.
+   *
+   * @param internalFrameMaximizeIcon the icon
+   */
+  public void setInternalFrameMaximizeIcon(IconUIResource internalFrameMaximizeIcon) {
+    this.internalFrameMaximizeIcon = internalFrameMaximizeIcon;
+  }
+
+  /**
+   * Gets the icon used in the close button in the title bar of {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the icon used in the close button in the title bar of {@link javax.swing.JInternalFrame}'s
+   */
+  public IconUIResource getInternalFrameCloseIcon() {
+    return internalFrameCloseIcon;
+  }
+
+  /**
+   * Sets the icon used in the close button in the title bar of {@link javax.swing.JInternalFrame}'s.
+   *
+   * @param internalFrameCloseIcon the icon
+   */
+  public void setInternalFrameCloseIcon(IconUIResource internalFrameCloseIcon) {
+    this.internalFrameCloseIcon = internalFrameCloseIcon;
+  }
+
+  /**
+   * Gets the border used around {@link javax.swing.JInternalFrame}'s.
+   *
+   * @return the border used around {@link javax.swing.JInternalFrame}'s
+   */
+  public BorderUIResource getInternalFrameBorder() {
+    return internalFrameBorder;
+  }
+
+  /**
+   * Sets the border used around {@link javax.swing.JInternalFrame}'s.
+   *
+   * @param internalFrameBorder the border used around {@link javax.swing.JInternalFrame}'s
+   */
+  public void setInternalFrameBorder(BorderUIResource internalFrameBorder) {
+    this.internalFrameBorder = internalFrameBorder;
+  }
+
+  /**
+   * Gets the font used in the title of {@link javax.swing.JInternalFrame}'s. Defaults to the text font with bold
+   * style.
+   *
+   * @return the font used in the title of {@link javax.swing.JInternalFrame}'s
+   */
+  public FontUIResource getInternalFrameTitleFont() {
+    return internalFrameTitleFont.getFont();
+  }
+
+  /**
+   * Sets the font used in the title of {@link javax.swing.JInternalFrame}'s. Defaults to the text font with bold
+   * style.
+   *
+   * @param internalFrameTitleFont the font
+   */
+  public void setInternalFrameTitleFont(FontUIResource internalFrameTitleFont) {
+    this.internalFrameTitleFont.setFont(internalFrameTitleFont);
+  }
+
+  /**
+   * Sets the background color for {@link javax.swing.JComponent}'s.
+   *
+   * @param color the control color
+   */
+  public void setControlColor(Color color) {
+    this.controlColor.setColor(color);
+    updateColors();
+  }
+
+  /**
+   * Sets the primary control background color used in scroll bar knobs etc.
+   *
+   * @param c the primary control background color
+   */
+  public void setPrimaryControlColor(Color c) {
+    primaryControlColor.setColor(c);
+    updateColors();
+  }
+
+  /**
+   * Sets the background color used in {@link javax.swing.JViewport}, {@link javax.swing.JTree}, {@link
+   * javax.swing.JTable} etc.
+   *
+   * @param c the background color used in {@link javax.swing.JViewport}, {@link javax.swing.JTree}, {@link
+   *          javax.swing.JTable} etc.
+   */
+  public void setBackgroundColor(Color c) {
+    backgroundColor.setColor(c);
+    updateColors();
+  }
+
+  /**
+   * Sets the text color.
+   *
+   * @param c the text color
+   */
+  public void setTextColor(Color c) {
+    textColor.setColor(c);
+    updateColors();
+  }
+
+  /**
+   * Gets the font used in {@link javax.swing.JOptionPane} buttons. Defaults to the text font with bold style.
+   *
+   * @return the font used in {@link javax.swing.JOptionPane} buttons
+   */
+  public FontUIResource getOptionPaneButtonFont() {
+    return optionPaneButtonFont.getFont();
+  }
+
+  /**
+   * Sets the font used in {@link javax.swing.JOptionPane} buttons. Defaults to the text font with bold style.
+   *
+   * @param optionPaneButtonFont the font used in {@link javax.swing.JOptionPane} buttons
+   */
+  public void setOptionPaneButtonFont(FontUIResource optionPaneButtonFont) {
+    this.optionPaneButtonFont.setFont(optionPaneButtonFont);
+  }
+
+  /**
+   * Gets the size of the {@link javax.swing.JSplitPane} divider.
+   *
+   * @return the size of the {@link javax.swing.JSplitPane} divider
+   */
+  public int getSplitPaneDividerSize() {
+    return splitPaneDividerSize;
+  }
+
+  /**
+   * Sets the size of the {@link javax.swing.JSplitPane} divider.
+   *
+   * @param splitPaneDividerSize the size of the {@link javax.swing.JSplitPane} divider
+   */
+  public void setSplitPaneDividerSize(int splitPaneDividerSize) {
+    this.splitPaneDividerSize = splitPaneDividerSize;
+  }
+
+  /**
+   * Gets the border used around {@link javax.swing.JTextField} (including spinners etc.).
+   *
+   * @return the border used around {@link javax.swing.JTextField}
+   */
+  public BorderUIResource getTextFieldBorder() {
+    return textFieldBorder.getBorder();
+  }
+
+  /**
+   * Sets the border used around {@link javax.swing.JTextField} (including spinners etc.).
+   *
+   * @param textFieldBorder the border used around {@link javax.swing.JTextField}
+   */
+  public void setTextFieldBorder(BorderUIResource textFieldBorder) {
+    this.textFieldBorder.setBorder(textFieldBorder);
+  }
+
+  /**
+   * Gets the icon used with open nodes in a {@link javax.swing.JTree}.
+   *
+   * @return the icon used with open nodes in a {@link javax.swing.JTree}
+   */
+  public IconUIResource getTreeOpenIcon() {
+    return treeOpenIcon;
+  }
+
+  /**
+   * Sets the icon used with open nodes in a {@link javax.swing.JTree}.
+   *
+   * @param treeOpenIcon the icon used with open nodes in a {@link javax.swing.JTree}
+   */
+  public void setTreeOpenIcon(IconUIResource treeOpenIcon) {
+    this.treeOpenIcon = treeOpenIcon;
+  }
+
+  /**
+   * Gets the icon used with closed nodes in a {@link javax.swing.JTree}.
+   *
+   * @return the icon used with closed nodes in a {@link javax.swing.JTree}
+   */
+  public IconUIResource getTreeClosedIcon() {
+    return treeClosedIcon;
+  }
+
+  /**
+   * Sets the icon used with closed nodes in a {@link javax.swing.JTree}.
+   *
+   * @param treeClosedIcon the icon used with closed nodes in a {@link javax.swing.JTree}
+   */
+  public void setTreeClosedIcon(IconUIResource treeClosedIcon) {
+    this.treeClosedIcon = treeClosedIcon;
+  }
+
+  /**
+   * Gets the icon used with leaf nodes in a {@link javax.swing.JTree}.
+   *
+   * @return the icon used with leaf nodes in a {@link javax.swing.JTree}
+   */
+  public IconUIResource getTreeLeafIcon() {
+    return treeLeafIcon;
+  }
+
+  /**
+   * Sets the icon used with leaf nodes in a {@link javax.swing.JTree}.
+   *
+   * @param treeLeafIcon the icon used with leaf nodes in a {@link javax.swing.JTree}
+   */
+  public void setTreeLeafIcon(IconUIResource treeLeafIcon) {
+    this.treeLeafIcon = treeLeafIcon;
+  }
+
+  /**
+   * Gets the border used around {@link javax.swing.JMenuBar}'s.
+   *
+   * @return the border used around {@link javax.swing.JMenuBar}'s
+   */
+  public BorderUIResource getMenuBarBorder() {
+    return menuBarBorder.getBorder();
+  }
+
+  /**
+   * Sets the border used around {@link javax.swing.JMenuBar}'s.
+   *
+   * @param menuBarBorder the border used around {@link javax.swing.JMenuBar}'s
+   */
+  public void setMenuBarBorder(BorderUIResource menuBarBorder) {
+    this.menuBarBorder.setBorder(menuBarBorder);
+  }
+
+  /**
+   * Sets the selected text background color.
+   *
+   * @param selectedTextBackgroundColor the selected text background color
+   */
+  public void setSelectedTextBackgroundColor(Color selectedTextBackgroundColor) {
+    this.selectedTextBackgroundColor.setColor(selectedTextBackgroundColor);
+    updateColors();
+  }
+
+  /**
+   * Sets the focus marker color.
+   *
+   * @param focusColor the focus marker color
+   */
+  public void setFocusColor(Color focusColor) {
+    this.focusColor.setColor(focusColor);
+  }
+
+  /**
+   * Sets the selected text color.
+   *
+   * @param selectedTextColor the selected text color
+   */
+  public void setSelectedTextColor(Color selectedTextColor) {
+    this.selectedTextColor.setColor(selectedTextColor);
+  }
+
+  /**
+   * Sets the tooltip background color.
+   *
+   * @param tooltipBackgroundColor the tooltip background color
+   */
+  public void setTooltipBackgroundColor(Color tooltipBackgroundColor) {
+    this.tooltipBackgroundColor.setColor(tooltipBackgroundColor);
+  }
+
+  /**
+   * Sets the background color for a {@link javax.swing.JDesktopPane}.
+   *
+   * @param desktopColor the background color for a {@link javax.swing.JDesktopPane}
+   */
+  public void setDesktopColor(Color desktopColor) {
+    this.desktopColor.setColor(desktopColor);
+    updateColors();
+  }
+
+  /**
+   * Sets the background color for the expand/collapse icons in a {@link javax.swing.JTree}.
+   *
+   * @param treeIconBackgroundColor the background color for the expand/collapse icons in a {@link javax.swing.JTree}
+   */
+  public void setTreeIconBackgroundColor(Color treeIconBackgroundColor) {
+    this.treeIconBackgroundColor.setColor(treeIconBackgroundColor);
+  }
+
+  /**
+   * Sets the background color for a selected menu item.
+   *
+   * @param selectedMenuBackgroundColor the background color for a selected menu item
+   */
+  public void setSelectedMenuBackgroundColor(Color selectedMenuBackgroundColor) {
+    this.selectedMenuBackgroundColor.setColor(selectedMenuBackgroundColor);
+    updateColors();
+  }
+
+  /**
+   * Sets the foreground color for a selected menu item.
+   *
+   * @param selectedMenuForegroundColor the foreground color for a selected menu item
+   */
+  public void setSelectedMenuForegroundColor(Color selectedMenuForegroundColor) {
+    this.selectedMenuForegroundColor.setColor(selectedMenuForegroundColor);
+  }
+
+  /**
+   * Sets the inactive text color.
+   *
+   * @param inactiveTextColor the inactive text color
+   */
+  public void setInactiveTextColor(Color inactiveTextColor) {
+    this.inactiveTextColor.setColor(inactiveTextColor);
+  }
+
+  /**
+   * Sets the {@link javax.swing.JScrollBar} background color.
+   *
+   * @param scrollBarBackgroundColor the {@link javax.swing.JScrollBar} background color
+   */
+  public void setScrollBarBackgroundColor(Color scrollBarBackgroundColor) {
+    this.scrollBarBackgroundColor.setColor(scrollBarBackgroundColor);
+    updateColors();
+  }
+
+  /**
+   * Sets the background color for the title of an active {@link javax.swing.JInternalFrame}.
+   *
+   * @param activeInternalFrameTitleBackgroundColor
+   *         the background color for the title of an active {@link javax.swing.JInternalFrame}
+   */
+  public void setActiveInternalFrameTitleBackgroundColor(Color activeInternalFrameTitleBackgroundColor) {
+    this.activeInternalFrameTitleBackgroundColor.setColor(activeInternalFrameTitleBackgroundColor);
+    updateColors();
+  }
+
+  /**
+   * Sets the foreground color for the title of an active {@link javax.swing.JInternalFrame}.
+   *
+   * @param activeInternalFrameTitleForegroundColor
+   *         the background color for the title of an active {@link javax.swing.JInternalFrame}
+   */
+  public void setActiveInternalFrameTitleForegroundColor(Color activeInternalFrameTitleForegroundColor) {
+    this.activeInternalFrameTitleForegroundColor.setColor(activeInternalFrameTitleForegroundColor);
+    updateColors();
+  }
+
+  /**
+   * Sets the gradient color for the title of an active {@link javax.swing.JInternalFrame}.
+   *
+   * @param activeInternalFrameTitleGradientColor
+   *         the gradient color for the title of an active {@link javax.swing.JInternalFrame}
+   */
+  public void setActiveInternalFrameTitleGradientColor(Color activeInternalFrameTitleGradientColor) {
+    this.activeInternalFrameTitleGradientColor.setColor(activeInternalFrameTitleGradientColor);
+    updateColors();
+  }
+
+  /**
+   * Sets the background color for the title of an inactive {@link javax.swing.JInternalFrame}.
+   *
+   * @param inactiveInternalFrameTitleBackgroundColor
+   *         the background color for the title of an inactive {@link javax.swing.JInternalFrame}
+   */
+  public void setInactiveInternalFrameTitleBackgroundColor(Color inactiveInternalFrameTitleBackgroundColor) {
+    this.inactiveInternalFrameTitleBackgroundColor.setColor(inactiveInternalFrameTitleBackgroundColor);
+    updateColors();
+  }
+
+  /**
+   * Sets the foreground color for the title of an inactive {@link javax.swing.JInternalFrame}.
+   *
+   * @param inactiveInternalFrameTitleForegroundColor
+   *         the background color for the title of an active {@link javax.swing.JInternalFrame}
+   */
+  public void setInactiveInternalFrameTitleForegroundColor(Color inactiveInternalFrameTitleForegroundColor) {
+    this.inactiveInternalFrameTitleForegroundColor.setColor(inactiveInternalFrameTitleForegroundColor);
+    updateColors();
+  }
+
+  /**
+   * Sets the gradient color for the title of an inactive {@link javax.swing.JInternalFrame}.
+   *
+   * @param inactiveInternalFrameTitleGradientColor
+   *         the gradient color for the title of an inactive {@link javax.swing.JInternalFrame}
+   */
+  public void setInactiveInternalFrameTitleGradientColor(Color inactiveInternalFrameTitleGradientColor) {
+    this.inactiveInternalFrameTitleGradientColor.setColor(inactiveInternalFrameTitleGradientColor);
+    updateColors();
+  }
+
+  /**
+   * Sets the title font of an {@link javax.swing.JInternalFrame}.
+   *
+   * @param frameTitleFont the title font of an {@link javax.swing.JInternalFrame}
+   */
+  public void setInternalFrameTitleFont(Font frameTitleFont) {
+    this.internalFrameTitleFont.setFont(frameTitleFont);
+  }
+
+  /**
+   * Sets the button font for a {@link javax.swing.JOptionPane}. Default to the text font with bold style.
+   *
+   * @param optionPaneButtonFont the button font for a {@link javax.swing.JOptionPane}
+   */
+  public void setOptionPaneButtonFont(Font optionPaneButtonFont) {
+    this.optionPaneButtonFont.setFont(optionPaneButtonFont);
+  }
+
+  /**
+   * Sets the border for the cells of a {@link javax.swing.table.JTableHeader}.
+   *
+   * @param tableHeaderCellBorder the border for the cells of a {@link javax.swing.table.JTableHeader}
+   */
+  public void setTableHeaderCellBorder(BorderUIResource tableHeaderCellBorder) {
+    this.tableHeaderCellBorder.setBorder(tableHeaderCellBorder);
+  }
+
+  /**
+   * Gets the width of a {@link javax.swing.JScrollBar}.
+   *
+   * @return the width of a {@link javax.swing.JScrollBar}
+   */
+  public int getScrollBarWidth() {
+    return scrollBarWidth;
+  }
+
+  /**
+   * Sets the width of a {@link javax.swing.JScrollBar}.
+   *
+   * @param scrollBarWidth the width of a {@link javax.swing.JScrollBar}
+   */
+  public void setScrollBarWidth(int scrollBarWidth) {
+    this.scrollBarWidth = scrollBarWidth;
+  }
+
+  /**
+   * Gets the margin of a {@link javax.swing.JButton}.
+   *
+   * @return the margin of a {@link javax.swing.JButton}
+   */
+  public InsetsUIResource getButtonMargin() {
+    return buttonMargin;
+  }
+
+  /**
+   * Sets the margin of a {@link javax.swing.JButton}.
+   *
+   * @param buttonMargin the margin of a {@link javax.swing.JButton}
+   */
+  public void setButtonMargin(InsetsUIResource buttonMargin) {
+    this.buttonMargin = buttonMargin;
+  }
+
+  /**
+   * Gets the border of a {@link javax.swing.JPopupMenu}.
+   *
+   * @return the border of a {@link javax.swing.JPopupMenu}
+   */
+  public BorderUIResource getPopupMenuBorder() {
+    return popupMenuBorder.getBorder();
+  }
+
+  /**
+   * Sets the border of a {@link javax.swing.JPopupMenu}.
+   *
+   * @param popupMenuBorder the border of a {@link javax.swing.JPopupMenu}
+   */
+  public void setPopupMenuBorder(BorderUIResource popupMenuBorder) {
+    this.popupMenuBorder.setBorder(popupMenuBorder);
+  }
+
+  /**
+   * Gets the icon used in the iconify button in the title of a {@link javax.swing.JInternalFrame}.
+   *
+   * @return the icon used in the iconify button in the title of a {@link javax.swing.JInternalFrame}
+   */
+  public IconUIResource getInternalFrameIconifyIcon() {
+    return internalFrameIconifyIcon;
+  }
+
+  /**
+   * Sets the icon used in the iconify button in the title of a {@link javax.swing.JInternalFrame}.
+   *
+   * @param internalFrameIconifyIcon the icon used in the iconify button in the title of a {@link
+   *                                 javax.swing.JInternalFrame}
+   */
+  public void setInternalFrameIconifyIcon(IconUIResource internalFrameIconifyIcon) {
+    this.internalFrameIconifyIcon = internalFrameIconifyIcon;
+  }
+
+  /**
+   * Gets the background color used in {@link javax.swing.JToolTip}.
+   *
+   * @return the background color used in {@link javax.swing.JToolTip}
+   */
+  public ColorUIResource getTooltipForegroundColor() {
+    return tooltipForegroundColor.getColor();
+  }
+
+  /**
+   * Sets the foreground color used in {@link javax.swing.JToolTip}.
+   *
+   * @param tooltipForegroundColor the foreground color used in {@link javax.swing.JToolTip}
+   */
+  public void setTooltipForegroundColor(ColorUIResource tooltipForegroundColor) {
+    this.tooltipForegroundColor.setColor(tooltipForegroundColor);
+    updateColors();
+  }
+
+  /**
+   * Gets the border used around list items in {@link javax.swing.JList}'s and {@link javax.swing.JComboBox}'s.
+   *
+   * @return the border used around list items in {@link javax.swing.JList}'s and {@link javax.swing.JComboBox}'s
+   */
+  public BorderUIResource getListItemBorder() {
+    return listItemBorder.getBorder();
+  }
+
+  /**
+   * Sets the border used around list items in {@link javax.swing.JList}'s and {@link javax.swing.JComboBox}'s.
+   *
+   * @param listItemBorder the border used around list items in {@link javax.swing.JList}'s and {@link javax.swing.JComboBox}'s
+   */
+  public void setListItemBorder(BorderUIResource listItemBorder) {
+    this.listItemBorder.setBorder(listItemBorder);
+  }
+
+  /**
+   * Gets the border used around the focused list item in {@link javax.swing.JList}'s and {@link javax.swing.JComboBox}'s.
+   *
+   * @return the border used around the focused list item in {@link javax.swing.JList}'s and {@link javax.swing.JComboBox}'s
+   */
+  public BorderUIResource getListFocusedItemBorder() {
+    return listFocusedItemBorder.getBorder();
+  }
+
+  /**
+   * Sets the border used around the focused list item in {@link javax.swing.JList}'s and {@link javax.swing.JComboBox}'s.
+   *
+   * @param listFocusedItemBorder the border used around the focused list item in {@link javax.swing.JList}'s and {@link javax.swing.JComboBox}'s
+   */
+  public void setListFocusedItemBorder(BorderUIResource listFocusedItemBorder) {
+    this.listFocusedItemBorder.setBorder(listFocusedItemBorder);
+  }
+
+}
diff --git a/src/net/infonode/gui/laf/InfoNodeLookAndFeelThemes.java b/src/net/infonode/gui/laf/InfoNodeLookAndFeelThemes.java
new file mode 100644
index 0000000..1380f2f
--- /dev/null
+++ b/src/net/infonode/gui/laf/InfoNodeLookAndFeelThemes.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InfoNodeLookAndFeelThemes.java,v 1.17 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.laf;
+
+import net.infonode.gui.Colors;
+import net.infonode.util.ColorUtil;
+
+import java.awt.*;
+
+/**
+ * Contains some predefined InfoNode look and feel themes.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.17 $
+ */
+public class InfoNodeLookAndFeelThemes {
+  private InfoNodeLookAndFeelThemes() {
+  }
+
+  /**
+   * A theme with dark blue controls and green selection.
+   *
+   * @return the theme
+   */
+  public static InfoNodeLookAndFeelTheme getDarkBlueGreenTheme() {
+    float hue = Colors.ROYAL_BLUE_HUE;
+    float saturation = 0.20f;
+
+    return new InfoNodeLookAndFeelTheme("Dark Blue Green Theme",
+                                        Color.getHSBColor(hue, saturation, 0.5f),
+                                        Color.getHSBColor(hue, saturation + 0.1f, 0.9f),
+                                        Color.getHSBColor(Colors.SAND_HUE, 0.04f, 0.5f),
+                                        Color.WHITE,
+                                        new Color(0, 170, 0),
+                                        Color.WHITE,
+                                        0.8);
+  }
+
+  /**
+   * A theme with light gray controls and blue selection.
+   *
+   * @return the theme
+   */
+  public static InfoNodeLookAndFeelTheme getGrayTheme() {
+    float hue = Colors.ROYAL_BLUE_HUE;
+    float saturation = 0.4f;
+
+    return new InfoNodeLookAndFeelTheme("Gray Theme",
+                                        new Color(220, 220, 220),
+                                        Color.getHSBColor(hue, saturation + 0.3f, 1f),
+                                        InfoNodeLookAndFeelTheme.DEFAULT_BACKGROUND_COLOR,
+                                        InfoNodeLookAndFeelTheme.DEFAULT_TEXT_COLOR,
+                                        Color.getHSBColor(hue, saturation, 1.0f),
+                                        Color.BLACK);
+  }
+
+  /**
+   * A theme with light blue controls and blue selection.
+   *
+   * @return the theme
+   */
+  public static InfoNodeLookAndFeelTheme getBlueIceTheme() {
+    float hue = Colors.ROYAL_BLUE_HUE;
+    float saturation = 0.4f;
+
+    Color color = new Color(197, 206, 218);
+    InfoNodeLookAndFeelTheme theme = new InfoNodeLookAndFeelTheme("Blue Ice Theme",
+                                                                  color,
+                                                                  Color.getHSBColor(hue, saturation + 0.3f, 1f),
+                                                                  InfoNodeLookAndFeelTheme.DEFAULT_BACKGROUND_COLOR,
+                                                                  InfoNodeLookAndFeelTheme.DEFAULT_TEXT_COLOR,
+                                                                  Color.getHSBColor(hue, saturation - 0.15f, 0.9f),
+                                                                  Color.BLACK,
+                                                                  0.3);
+    theme.setPrimaryControlColor(ColorUtil.mult(color, 0.9));
+    return theme;
+  }
+
+  /**
+   * A low contrast theme with a softer tone.
+   *
+   * @return the theme
+   */
+  public static InfoNodeLookAndFeelTheme getSoftGrayTheme() {
+    InfoNodeLookAndFeelTheme theme = new InfoNodeLookAndFeelTheme("Soft Gray Theme",
+                                                                  /*new Color(230, 225, 217)*/new Color(230, 230, 233),
+                                                                  new Color(212, 220, 236),
+                                                                  new Color(255, 255, 255),
+                                                                  new Color(0, 0, 0),
+                                                                  new Color(76, 113, 188),
+                                                                  new Color(255, 255, 255),
+                                                                  0.2);
+    theme.setActiveInternalFrameTitleBackgroundColor(new Color(76, 113, 188));
+    //theme.setActiveInternalFrameTitleBackgroundColor(new Color(118, 146, 204));
+    theme.setActiveInternalFrameTitleForegroundColor(Color.WHITE);
+    theme.setActiveInternalFrameTitleGradientColor(new Color(80, 135, 248));
+    //theme.setActiveInternalFrameTitleGradientColor(new Color(149, 182, 251));
+    theme.setInactiveInternalFrameTitleBackgroundColor(new Color(220, 220, 222)/*new Color(210, 206, 198)*/);
+    theme.setInactiveInternalFrameTitleForegroundColor(Color.BLACK);
+    theme.setInactiveInternalFrameTitleGradientColor(new Color(240, 240, 243));
+
+    return theme;
+  }
+
+
+}
diff --git a/src/net/infonode/gui/laf/info/Info.java b/src/net/infonode/gui/laf/info/Info.java
new file mode 100644
index 0000000..b51bb5d
--- /dev/null
+++ b/src/net/infonode/gui/laf/info/Info.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Info.java,v 1.3 2004/09/21 08:42:19 johan Exp $
+package net.infonode.gui.laf.info;
+
+import net.infonode.gui.ReleaseInfoDialog;
+import net.infonode.gui.laf.InfoNodeLookAndFeelReleaseInfo;
+
+/**
+ * @author $Author: johan $
+ * @version $Revision: 1.3 $
+ */
+public class Info {
+  public static final void main(String[] args) {
+    ReleaseInfoDialog.showDialog(InfoNodeLookAndFeelReleaseInfo.getReleaseInfo(), null);
+    System.exit(0);
+  }
+}
diff --git a/src/net/infonode/gui/laf/package.html b/src/net/infonode/gui/laf/package.html
new file mode 100644
index 0000000..2c800cb
--- /dev/null
+++ b/src/net/infonode/gui/laf/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Contains InfoNode Look and Feel classes.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/gui/laf/ui/SlimComboBoxUI.java b/src/net/infonode/gui/laf/ui/SlimComboBoxUI.java
new file mode 100644
index 0000000..7a5e47e
--- /dev/null
+++ b/src/net/infonode/gui/laf/ui/SlimComboBoxUI.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SlimComboBoxUI.java,v 1.6 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.laf.ui;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.border.LineBorder;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicComboBoxRenderer;
+import javax.swing.plaf.metal.MetalComboBoxUI;
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class SlimComboBoxUI extends MetalComboBoxUI {
+  public static Border FOCUS_BORDER = new CompoundBorder(new LineBorder(Color.BLACK), new EmptyBorder(0, 3, 0, 3));
+  public static Border NORMAL_BORDER = new EmptyBorder(1, 4, 1, 4);
+
+  public static ComponentUI createUI(JComponent b) {
+    return new SlimComboBoxUI();
+  }
+
+  protected ListCellRenderer createRenderer() {
+    return new BasicComboBoxRenderer() {
+      public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
+                                                    boolean cellHasFocus) {
+        JLabel label = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+        label.setBorder(index == -1 ? noFocusBorder : cellHasFocus ? FOCUS_BORDER : NORMAL_BORDER);
+        return label;
+      }
+    };
+  }
+
+
+}
diff --git a/src/net/infonode/gui/laf/ui/SlimInternalFrameTitlePane.java b/src/net/infonode/gui/laf/ui/SlimInternalFrameTitlePane.java
new file mode 100644
index 0000000..67618f4
--- /dev/null
+++ b/src/net/infonode/gui/laf/ui/SlimInternalFrameTitlePane.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SlimInternalFrameTitlePane.java,v 1.4 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.laf.ui;
+
+import net.infonode.gui.ButtonFactory;
+import net.infonode.gui.border.EdgeBorder;
+
+import javax.swing.*;
+import javax.swing.plaf.basic.BasicInternalFrameTitlePane;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class SlimInternalFrameTitlePane extends BasicInternalFrameTitlePane {
+  public SlimInternalFrameTitlePane(JInternalFrame f) {
+    super(f);
+    setBorder(new EdgeBorder(UIManager.getColor("controlDkShadow"), false, true, false, false));
+  }
+
+  protected void createButtons() {
+    iconButton = ButtonFactory.createFlatHighlightButton(iconIcon,
+                                                         UIManager.getString("InternalFrame.iconButtonToolTip"),
+                                                         0,
+                                                         iconifyAction);
+    iconButton.setFocusable(false);
+
+    closeButton = ButtonFactory.createFlatHighlightButton(closeIcon,
+                                                          UIManager.getString("InternalFrame.closeButtonToolTip"),
+                                                          0,
+                                                          closeAction);
+    closeButton.setFocusable(false);
+
+    maxButton = ButtonFactory.createFlatHighlightButton(maxIcon,
+                                                        UIManager.getString("InternalFrame.maxButtonToolTip"),
+                                                        0,
+                                                        maximizeAction);
+    maxButton.setFocusable(false);
+  }
+
+}
diff --git a/src/net/infonode/gui/laf/ui/SlimInternalFrameUI.java b/src/net/infonode/gui/laf/ui/SlimInternalFrameUI.java
new file mode 100644
index 0000000..495beb5
--- /dev/null
+++ b/src/net/infonode/gui/laf/ui/SlimInternalFrameUI.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SlimInternalFrameUI.java,v 1.4 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.laf.ui;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicInternalFrameUI;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class SlimInternalFrameUI extends BasicInternalFrameUI {
+
+  public static ComponentUI createUI(JComponent b) {
+    return new SlimInternalFrameUI((JInternalFrame) b);
+  }
+
+  public SlimInternalFrameUI(JInternalFrame b) {
+    super(b);
+  }
+
+  protected JComponent createNorthPane(JInternalFrame w) {
+    return new SlimInternalFrameTitlePane(w);
+  }
+}
diff --git a/src/net/infonode/gui/laf/ui/SlimMenuItemUI.java b/src/net/infonode/gui/laf/ui/SlimMenuItemUI.java
new file mode 100644
index 0000000..3e73d2c
--- /dev/null
+++ b/src/net/infonode/gui/laf/ui/SlimMenuItemUI.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SlimMenuItemUI.java,v 1.4 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.laf.ui;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicMenuItemUI;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class SlimMenuItemUI extends BasicMenuItemUI {
+
+  public static ComponentUI createUI(JComponent b) {
+    return new SlimMenuItemUI();
+  }
+
+  protected void installDefaults() {
+    super.installDefaults();
+  }
+
+}
diff --git a/src/net/infonode/gui/laf/ui/SlimSplitPaneDivider.java b/src/net/infonode/gui/laf/ui/SlimSplitPaneDivider.java
new file mode 100644
index 0000000..33a4fce
--- /dev/null
+++ b/src/net/infonode/gui/laf/ui/SlimSplitPaneDivider.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SlimSplitPaneDivider.java,v 1.3 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.laf.ui;
+
+import net.infonode.gui.icon.button.ArrowIcon;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import javax.swing.plaf.basic.BasicSplitPaneDivider;
+import javax.swing.plaf.basic.BasicSplitPaneUI;
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class SlimSplitPaneDivider extends BasicSplitPaneDivider {
+  public SlimSplitPaneDivider(BasicSplitPaneUI ui) {
+    super(ui);
+  }
+
+  protected JButton createLeftOneTouchButton() {
+    ArrowIcon icon = new ArrowIcon(8, Direction.LEFT);
+    icon.setShadowEnabled(false);
+    JButton button = new JButton(icon);
+    button.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    button.setFocusPainted(false);
+    button.setBorderPainted(false);
+    button.setRequestFocusEnabled(false);
+    return button;
+  }
+
+  protected JButton createRightOneTouchButton() {
+    ArrowIcon icon = new ArrowIcon(8, Direction.RIGHT);
+    icon.setShadowEnabled(false);
+    JButton button = new JButton(icon);
+    button.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+    button.setFocusPainted(false);
+    button.setBorderPainted(false);
+    button.setRequestFocusEnabled(false);
+    return button;
+  }
+}
diff --git a/src/net/infonode/gui/laf/ui/SlimSplitPaneUI.java b/src/net/infonode/gui/laf/ui/SlimSplitPaneUI.java
new file mode 100644
index 0000000..378e8b0
--- /dev/null
+++ b/src/net/infonode/gui/laf/ui/SlimSplitPaneUI.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SlimSplitPaneUI.java,v 1.3 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.laf.ui;
+
+import javax.swing.*;
+import javax.swing.plaf.ComponentUI;
+import javax.swing.plaf.basic.BasicSplitPaneDivider;
+import javax.swing.plaf.basic.BasicSplitPaneUI;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class SlimSplitPaneUI extends BasicSplitPaneUI {
+  public static ComponentUI createUI(JComponent c) {
+    return new SlimSplitPaneUI();
+  }
+
+  public BasicSplitPaneDivider createDefaultDivider() {
+    return new SlimSplitPaneDivider(this);
+  }
+
+}
diff --git a/src/net/infonode/gui/laf/value/BorderValue.java b/src/net/infonode/gui/laf/value/BorderValue.java
new file mode 100644
index 0000000..1b7bc1d
--- /dev/null
+++ b/src/net/infonode/gui/laf/value/BorderValue.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BorderValue.java,v 1.3 2004/09/22 14:35:04 jesper Exp $
+package net.infonode.gui.laf.value;
+
+import javax.swing.border.Border;
+import javax.swing.plaf.BorderUIResource;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class BorderValue {
+  private BorderUIResource defaultBorder;
+  private BorderUIResource border;
+
+  public BorderValue() {
+  }
+
+  public BorderValue(Border defaultBorder) {
+    this.defaultBorder = new BorderUIResource(defaultBorder);
+  }
+
+  public BorderUIResource getDefaultBorder() {
+    return defaultBorder;
+  }
+
+  public void setDefaultBorder(Border defaultBorder) {
+    setDefaultBorder(new BorderUIResource(defaultBorder));
+  }
+
+  public void setDefaultBorder(BorderUIResource defaultBorder) {
+    this.defaultBorder = defaultBorder;
+  }
+
+  public BorderUIResource getBorder() {
+    return border == null ? defaultBorder : border;
+  }
+
+  public void setBorder(BorderUIResource border) {
+    this.border = border;
+  }
+}
diff --git a/src/net/infonode/gui/laf/value/ColorValue.java b/src/net/infonode/gui/laf/value/ColorValue.java
new file mode 100644
index 0000000..78e1a92
--- /dev/null
+++ b/src/net/infonode/gui/laf/value/ColorValue.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ColorValue.java,v 1.3 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.laf.value;
+
+import javax.swing.plaf.ColorUIResource;
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class ColorValue {
+  private ColorUIResource color;
+  private ColorUIResource defaultColor;
+
+  public ColorValue() {
+    this(Color.BLACK);
+  }
+
+  public ColorValue(int r, int g, int b) {
+    this(new ColorUIResource(r, g, b));
+  }
+
+  public ColorValue(Color defaultColor) {
+    this(new ColorUIResource(defaultColor));
+  }
+
+  public ColorValue(ColorUIResource defaultColor) {
+    this.defaultColor = defaultColor;
+  }
+
+  public ColorUIResource getColor() {
+    return color == null ? defaultColor : color;
+  }
+
+  public void setColor(Color color) {
+    setColor(new ColorUIResource(color));
+  }
+
+  public void setColor(ColorUIResource color) {
+    this.color = color;
+  }
+
+  public void setDefaultColor(Color defaultColor) {
+    setDefaultColor(new ColorUIResource(defaultColor));
+  }
+
+  public void setDefaultColor(ColorUIResource defaultColor) {
+    this.defaultColor = defaultColor;
+  }
+
+  public void setDefaultColor(ColorValue defaultColor) {
+    setDefaultColor(defaultColor.getColor());
+  }
+}
diff --git a/src/net/infonode/gui/laf/value/FontValue.java b/src/net/infonode/gui/laf/value/FontValue.java
new file mode 100644
index 0000000..777f2fe
--- /dev/null
+++ b/src/net/infonode/gui/laf/value/FontValue.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FontValue.java,v 1.3 2005/02/16 11:28:11 jesper Exp $
+package net.infonode.gui.laf.value;
+
+import javax.swing.plaf.FontUIResource;
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class FontValue {
+  private static final FontUIResource DEFAULT_FONT = new FontUIResource("Dialog", 0, 11);
+
+  private FontUIResource font;
+  private FontUIResource defaultFont;
+
+  public FontValue() {
+    this(DEFAULT_FONT);
+  }
+
+  public FontValue(FontUIResource defaultFont) {
+    this.defaultFont = defaultFont;
+  }
+
+  public void setFont(Font font) {
+    setFont(new FontUIResource(font));
+  }
+
+  public void setFont(FontUIResource font) {
+    this.font = font;
+  }
+
+  public void setDefaultFont(FontUIResource defaultFont) {
+    this.defaultFont = defaultFont;
+  }
+
+  public FontUIResource getFont() {
+    return font == null ? defaultFont : font;
+  }
+}
diff --git a/src/net/infonode/gui/layout/BorderLayout2.java b/src/net/infonode/gui/layout/BorderLayout2.java
new file mode 100644
index 0000000..82a2aa2
--- /dev/null
+++ b/src/net/infonode/gui/layout/BorderLayout2.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BorderLayout2.java,v 1.8 2005/02/16 11:28:12 jesper Exp $
+package net.infonode.gui.layout;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class BorderLayout2 implements LayoutManager2 {
+  private Component[][] components;
+
+  public BorderLayout2() {
+    components = new Component[3][];
+
+    for (int i = 0; i < components.length; i++) {
+      components[i] = new Component[3];
+    }
+  }
+
+  public void addLayoutComponent(String name, Component comp) {
+  }
+
+  public void addLayoutComponent(Component comp, Object constraints) {
+    if (constraints instanceof Point) {
+      Point p = (Point) constraints;
+      components[p.x][p.y] = comp;
+    }
+    else
+      throw new RuntimeException("BorderLayout2 constraint must be a Point!");
+  }
+
+  public float getLayoutAlignmentX(Container target) {
+    return target.getAlignmentX();
+  }
+
+  public float getLayoutAlignmentY(Container target) {
+    return target.getAlignmentY();
+  }
+
+  public void invalidateLayout(Container target) {
+  }
+
+  public Dimension maximumLayoutSize(Container target) {
+    int width = 0;
+    int height = 0;
+
+    for (int i = 0; i < 3; i++) {
+      width += getMaximumWidth(i);
+      height += getMaximumHeight(i);
+    }
+
+    return new Dimension(width, height);
+  }
+
+  private int getPreferredHeight(int row) {
+    int maxHeight = 0;
+
+    for (int i = 0; i < 3; i++) {
+      Component c = components[i][row];
+
+      if (c != null && c.isVisible()) {
+        int height = c.getPreferredSize().height;
+
+        if (height > maxHeight)
+          maxHeight = height;
+      }
+    }
+
+    return maxHeight;
+  }
+
+//  private static int tt = 0;
+
+  private int getPreferredWidth(int column) {
+    int maxWidth = 0;
+
+    for (int i = 0; i < 3; i++) {
+      Component c = components[column][i];
+      if (c != null && c.isVisible()) {
+        int width = c.getPreferredSize().width;
+
+        if (width > maxWidth)
+          maxWidth = width;
+      }
+    }
+
+    return maxWidth;
+  }
+
+  private int getMinimumHeight(int row) {
+    int maxHeight = 0;
+
+    for (int i = 0; i < 3; i++) {
+      Component c = components[i][row];
+
+      if (c != null && c.isVisible()) {
+        int height = c.getMinimumSize().height;
+
+        if (height > maxHeight)
+          maxHeight = height;
+      }
+    }
+
+    return maxHeight;
+  }
+
+  private int getMinimumWidth(int column) {
+    int maxWidth = 0;
+
+    for (int i = 0; i < 3; i++) {
+      Component c = components[column][i];
+
+      if (c != null && c.isVisible()) {
+        int width = c.getMinimumSize().width;
+
+        if (width > maxWidth)
+          maxWidth = width;
+      }
+    }
+
+    return maxWidth;
+  }
+
+  private int getMaximumHeight(int row) {
+    int minHeight = Integer.MAX_VALUE;
+
+    for (int i = 0; i < 3; i++) {
+      Component c = components[i][row];
+
+      if (c != null && c.isVisible()) {
+        int height = c.getMaximumSize().height;
+
+        if (height < minHeight)
+          minHeight = height;
+      }
+    }
+
+    return minHeight;
+  }
+
+  private int getMaximumWidth(int column) {
+    int minWidth = 0;
+
+    for (int i = 0; i < 3; i++) {
+      Component c = components[column][i];
+
+      if (c != null && c.isVisible()) {
+        int width = c.getMaximumSize().width;
+
+        if (width < minWidth)
+          minWidth = width;
+      }
+    }
+
+    return minWidth;
+  }
+
+  private static void setBounds(Component component, Rectangle bounds) {
+    int width = Math.min(component.getMaximumSize().width, bounds.width);
+    int height = Math.min(component.getMaximumSize().height, bounds.height);
+    Rectangle r = new Rectangle(bounds.x + (int) (component.getAlignmentX() * (bounds.width - width)),
+                                bounds.y + (int) (component.getAlignmentY() * (bounds.height - height)),
+                                width,
+                                height);
+    component.setBounds(r);
+  }
+
+  public void layoutContainer(Container parent) {
+    //System.out.println("Laying out container: " + components[2][0].isVisible());
+    Insets insets = parent.getInsets();
+    Dimension innerSize = LayoutUtil.getInteriorSize(parent);
+    int[] w = {getPreferredWidth(0), getPreferredWidth(2)};
+    int[] h = {getPreferredHeight(0), getPreferredHeight(2)};
+
+    int y = insets.top;
+
+    for (int row = 0; row < 3; row++) {
+      int height = row == 1 ? innerSize.height - h[0] - h[1] : h[row / 2];
+      int x = insets.left;
+
+      for (int col = 0; col < 3; col++) {
+        int width = col == 1 ? innerSize.width - w[0] - w[1] : w[col / 2];
+        Component c = components[col][row];
+
+        if (c != null && c.isVisible()) {
+          setBounds(c, new Rectangle(x, y, width, height));
+        }
+
+        x += width;
+      }
+
+      y += height;
+    }
+    //System.out.println("Layout complete: " + components[2][0].isVisible());
+  }
+
+  public Dimension minimumLayoutSize(Container parent) {
+    int width = 0;
+    int height = 0;
+
+    for (int i = 0; i < 3; i++) {
+      width += getMinimumWidth(i);
+      height += getMinimumHeight(i);
+    }
+
+    return new Dimension(width, height);
+  }
+
+  public Dimension preferredLayoutSize(Container parent) {
+    int width = 0;
+    int height = 0;
+
+    for (int i = 0; i < 3; i++) {
+      width += getPreferredWidth(i);
+      height += getPreferredHeight(i);
+    }
+
+    return new Dimension(width, height);
+  }
+
+  public void removeLayoutComponent(Component comp) {
+    for (int col = 0; col < 3; col++) {
+      for (int row = 0; row < 3; row++) {
+        if (components[col][row] == comp) {
+          components[col][row] = null;
+          return;
+        }
+      }
+    }
+  }
+}
diff --git a/src/net/infonode/gui/layout/DirectionLayout.java b/src/net/infonode/gui/layout/DirectionLayout.java
new file mode 100644
index 0000000..bb34b8c
--- /dev/null
+++ b/src/net/infonode/gui/layout/DirectionLayout.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DirectionLayout.java,v 1.15 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.layout;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class DirectionLayout implements LayoutManager2 {
+  private static final Insets EMPTY_INSETS = new Insets(0, 0, 0, 0);
+
+  private Direction direction;
+  private HashMap componentInsets;
+  private int componentSpacing;
+  private boolean compressing;
+
+  private ArrayList layoutOrderList;
+
+  public DirectionLayout() {
+    this(Direction.RIGHT);
+  }
+
+  public DirectionLayout(int componentSpacing) {
+    this(Direction.RIGHT, componentSpacing);
+  }
+
+  public DirectionLayout(Direction direction) {
+    this(direction, 0);
+  }
+
+  public DirectionLayout(Direction direction, int componentSpacing) {
+    this.direction = direction;
+    this.componentSpacing = componentSpacing;
+  }
+
+  public int getComponentSpacing() {
+    return componentSpacing;
+  }
+
+  public void setComponentSpacing(int componentSpacing) {
+    this.componentSpacing = componentSpacing;
+  }
+
+  public void addLayoutComponent(String name, Component comp) {
+  }
+
+  public Direction getDirection() {
+    return direction;
+  }
+
+  public void setDirection(Direction direction) {
+    this.direction = direction;
+  }
+
+  public boolean isVertical() {
+    return !direction.isHorizontal();
+  }
+
+  public boolean isCompressing() {
+    return compressing;
+  }
+
+  public void setCompressing(boolean compressing) {
+    this.compressing = compressing;
+  }
+
+  public void setLayoutOrderList(ArrayList layoutOrderList) {
+    this.layoutOrderList = layoutOrderList;
+  }
+
+  private int getSize(Dimension d) {
+    return (int) (isVertical() ? d.getHeight() : d.getWidth());
+  }
+
+  private static int getBeforeSpacing(Insets insets) {
+    return insets.left;
+  }
+
+  private int getAfterSpacing(Component component, boolean isLast) {
+    return getInsets(component).right + (isLast ? 0 : componentSpacing);
+  }
+
+  private Dimension createSize(int size, int otherSize) {
+    return isVertical() ? new Dimension(otherSize, size) : new Dimension(size, otherSize);
+  }
+
+  private static Dimension getSize(Dimension interiorSize, Container component) {
+    Insets insets = component.getInsets();
+    return new Dimension(interiorSize.width + insets.left + insets.right,
+                         interiorSize.height + insets.top + insets.bottom);
+  }
+
+  private int getOtherSize(Dimension dim) {
+    return (int) (isVertical() ? dim.getWidth() : dim.getHeight());
+  }
+
+  private void setSize(Component component, int size, int otherSize) {
+    int maxOtherSize = getOtherSize(component.getMaximumSize());
+    component.setSize(createSize(size, Math.min(maxOtherSize, otherSize)));
+  }
+
+  public void layoutContainer(Container parent) {
+    Component[] components = getVisibleChildren(parent);
+    
+    /*System.out.println("Parent: " + parent.getComponentCount() + "  List: " + components.length);
+    for (int i = 0; i < components.length; i++) {
+    	System.out.println("Parent: " + parent.getComponent(i) + "  List: " + components[i]);
+    }*/
+    
+    int count = components.length;
+
+    if (components.length == 0)
+      return;
+
+    int totalSpacing = 0;
+
+    for (int i = 0; i < components.length; i++)
+      totalSpacing += getSpacing(components[i], i == components.length - 1);
+
+    boolean[] discarded = new boolean[components.length];
+    Dimension parentInteriorSize = LayoutUtil.getInteriorSize(parent);
+    int componentsTotalSize = getSize(parentInteriorSize) - totalSpacing;
+    int maxComponentSize = componentsTotalSize / components.length;
+    int lastCount;
+
+    int otherSize = getOtherSize(parentInteriorSize);
+    //System.out.println(parentInteriorSize);
+//    int otherSize = Math.min(getOtherSize(parentInteriorSize), getOtherSize(LayoutUtil.getMaxPreferredSize(components)));
+
+    // First, set componentsTotalSize of all components that fit inside the limit
+    do {
+      lastCount = count;
+
+      for (int i = 0; i < components.length; i++) {
+        if (!discarded[i]) {
+          int prefSize = getSize(components[i].getPreferredSize());
+
+          if (prefSize <= maxComponentSize) {
+            setSize(components[i], prefSize, otherSize);
+            componentsTotalSize -= prefSize;
+            discarded[i] = true;
+            count--;
+
+            if (count == 0)
+              break;
+
+            maxComponentSize = componentsTotalSize / count;
+          }
+        }
+      }
+    } while (lastCount > count);
+
+    if (count > 0) {
+      do {
+        lastCount = count;
+
+        // Now fit all that have a larger minimum componentsTotalSize
+        for (int i = 0; i < components.length; i++) {
+          if (!discarded[i]) {
+            int minSize = getSize(components[i].getMinimumSize());
+
+            if (minSize >= maxComponentSize) {
+              setSize(components[i], minSize, otherSize);
+              componentsTotalSize -= minSize;
+              discarded[i] = true;
+              count--;
+
+              if (count == 0)
+                break;
+
+              maxComponentSize = componentsTotalSize / count;
+            }
+          }
+        }
+      } while (lastCount > count);
+    }
+
+    Insets insets = parent.getInsets();
+
+    int pos = direction == Direction.RIGHT ? insets.left :
+              direction == Direction.DOWN ? insets.top :
+              direction == Direction.LEFT ? insets.right :
+              insets.bottom;
+
+    int yOffset = isVertical() ? insets.left : insets.top;
+
+    for (int i = 0; i < components.length; i++) {
+      pos += getBeforeSpacing(getInsets(components[i]));
+
+      if (!discarded[i]) {
+        int componentSize = Math.max(getSize(components[i].getMinimumSize()), componentsTotalSize / count);
+        setSize(components[i], componentSize, otherSize);
+        count--;
+        componentsTotalSize -= componentSize;
+      }
+
+      int yPos = yOffset + (int) ((otherSize - getOtherSize(components[i].getSize())) *
+                                  (direction == Direction.DOWN || direction == Direction.LEFT ?
+                                   1 - components[i].getAlignmentY() :
+                                   components[i].getAlignmentY()));
+
+      if (isVertical()) {
+        components[i].setLocation(yPos,
+                                  direction == Direction.DOWN ?
+                                  pos :
+                                  parent.getHeight() - pos - components[i].getHeight());
+      }
+      else {
+        components[i].setLocation(direction == Direction.RIGHT ?
+                                  pos :
+                                  parent.getWidth() - pos - components[i].getWidth(),
+                                  yPos);
+      }
+
+      pos += getSize(components[i].getSize()) + getAfterSpacing(components[i], i == components.length - 1);
+      //System.out.println("\n" + components[i] + " " + components[i].getLocation() + "  " + components[i].getSize() + "  " + ((JComponent)components[i]).getInsets());
+    }
+  }
+
+  public void setComponentInsets(Component c, Insets i) {
+    if (i == null) {
+      removeLayoutComponent(c);
+    }
+    else {
+      if (componentInsets == null)
+        componentInsets = new HashMap(4);
+
+      componentInsets.put(c, i);
+    }
+  }
+
+  private Component[] getVisibleChildren(Container parent) {
+    if (layoutOrderList != null) {
+      Component[] components = new Component[layoutOrderList.size()];
+      for (int i = 0; i < layoutOrderList.size(); i++)
+        components[i] = (Component) layoutOrderList.get(i);
+
+      return LayoutUtil.getVisibleChildren(components);
+    }
+
+    return LayoutUtil.getVisibleChildren(parent);
+  }
+
+  private int getSpacing(Component component, boolean isLast) {
+    Insets insets = getInsets(component);
+    return insets.left + insets.right + (isLast ? 0 : componentSpacing);
+  }
+
+  private Insets getInsets(Component component) {
+    Object o = componentInsets == null ? null : componentInsets.get(component);
+    return o == null ? EMPTY_INSETS : (Insets) o;
+  }
+
+  public Dimension minimumLayoutSize(Container parent) {
+    Component[] c = getVisibleChildren(parent);
+    int size = 0;
+    int maxHeight = 0;
+
+    for (int i = 0; i < c.length; i++) {
+      size += getSize(c[i].getMinimumSize()) + getSpacing(c[i], i == c.length - 1);
+      maxHeight = Math.max(getOtherSize(c[i].getMinimumSize()), maxHeight);
+    }
+
+    Dimension d = getSize(isVertical() ? new Dimension(maxHeight, size) : new Dimension(size, maxHeight), parent);
+    //System.out.println("Minimum size: " + d);
+    return d;
+  }
+
+  public Dimension preferredLayoutSize(Container parent) {
+    Component[] c = getVisibleChildren(parent);
+    int size = 0;
+    int maxHeight = 0;
+
+    for (int i = 0; i < c.length; i++) {
+      if (!compressing)
+        size += getSize(c[i].getPreferredSize()) + getSpacing(c[i], i == c.length - 1);
+
+      maxHeight = Math.max(getOtherSize(c[i].getPreferredSize()), maxHeight);
+    }
+
+    Dimension d = getSize(isVertical() ? new Dimension(maxHeight, size) : new Dimension(size, maxHeight), parent);
+    //System.out.println("Preferred size: " + d);
+    return d;
+  }
+
+  public void removeLayoutComponent(Component comp) {
+    if (componentInsets != null) {
+      componentInsets.remove(comp);
+
+      if (componentInsets.size() == 0)
+        componentInsets = null;
+    }
+  }
+
+  public void addLayoutComponent(Component comp, Object constraints) {
+    setComponentInsets(comp, (Insets) constraints);
+  }
+
+  public float getLayoutAlignmentX(Container target) {
+    return 0;
+  }
+
+  public float getLayoutAlignmentY(Container target) {
+    return 0;
+  }
+
+  public void invalidateLayout(Container target) {
+  }
+
+  public Dimension maximumLayoutSize(Container parent) {
+    Component[] c = getVisibleChildren(parent);
+    int size = 0;
+    int maxHeight = Integer.MAX_VALUE;
+
+    for (int i = 0; i < c.length; i++) {
+      size += getSize(c[i].getMaximumSize()) + getSpacing(c[i], i == c.length - 1);
+//      maxHeight = Math.min(getOtherSize(c[i].getMaximumSize()), maxHeight);
+    }
+
+    Dimension d = getSize(isVertical() ? new Dimension(maxHeight, size) : new Dimension(size, maxHeight), parent);
+    //System.out.println("Maximum size: " + d);
+    return d;
+  }
+}
diff --git a/src/net/infonode/gui/layout/LayoutUtil.java b/src/net/infonode/gui/layout/LayoutUtil.java
new file mode 100644
index 0000000..a2399f2
--- /dev/null
+++ b/src/net/infonode/gui/layout/LayoutUtil.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: LayoutUtil.java,v 1.11 2005/02/16 11:28:12 jesper Exp $
+package net.infonode.gui.layout;
+
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+public class LayoutUtil {
+  private LayoutUtil() {
+  }
+
+  public static Component[] getVisibleChildren(Container parent) {
+    return getVisibleChildren(parent.getComponents());
+  }
+
+  public static Component[] getVisibleChildren(Component[] components) {
+    int count = 0;
+
+    for (int i = 0; i < components.length; i++)
+      if (components[i].isVisible())
+        count++;
+
+    Component[] c = new Component[count];
+    int index = 0;
+
+    for (int i = 0; i < components.length; i++)
+      if (components[i].isVisible())
+        c[index++] = components[i];
+
+    return c;
+  }
+
+  public static Rectangle getInteriorArea(Container container) {
+    Insets insets = container.getInsets();
+    return new Rectangle(insets.left,
+                         insets.top,
+                         container.getWidth() - insets.left - insets.right,
+                         container.getHeight() - insets.top - insets.bottom);
+  }
+
+  public static Dimension getInteriorSize(Container container) {
+    Insets insets = container.getInsets();
+    return new Dimension(container.getWidth() - insets.left - insets.right,
+                         container.getHeight() - insets.top - insets.bottom);
+  }
+
+  public static Dimension rotate(Dimension dim, Direction dir) {
+    return rotate(dim, dir.isHorizontal());
+  }
+
+  public static Dimension rotate(Dimension dim, boolean horizontal) {
+    return dim == null ? null : horizontal ? dim : new Dimension(dim.height, dim.width);
+  }
+
+  public static boolean isDescendingFrom(Component component, Component parent) {
+    return component == parent || (component != null && isDescendingFrom(component.getParent(), parent));
+  }
+
+  public static Dimension getMaxMinimumSize(Component[] components) {
+    int maxWidth = 0;
+    int maxHeight = 0;
+
+    for (int i = 0; i < components.length; i++) {
+      if (components[i] != null) {
+        Dimension min = components[i].getMinimumSize();
+        int w = min.width;
+        int h = min.height;
+
+        if (maxHeight < h)
+          maxHeight = h;
+
+        if (maxWidth < w)
+          maxWidth = w;
+      }
+    }
+
+    return new Dimension(maxWidth, maxHeight);
+  }
+
+  public static Dimension getMaxPreferredSize(Component[] components) {
+    int maxWidth = 0;
+    int maxHeight = 0;
+
+    for (int i = 0; i < components.length; i++) {
+      if (components[i] != null) {
+        Dimension min = components[i].getPreferredSize();
+        int w = min.width;
+        int h = min.height;
+
+        if (maxHeight < h)
+          maxHeight = h;
+
+        if (maxWidth < w)
+          maxWidth = w;
+      }
+    }
+
+    return new Dimension(maxWidth, maxHeight);
+  }
+
+  public static Dimension getMinMaximumSize(Component[] components) {
+    int minWidth = Integer.MAX_VALUE;
+    int minHeight = Integer.MAX_VALUE;
+
+    for (int i = 0; i < components.length; i++) {
+      if (components[i] != null) {
+        Dimension min = components[i].getMaximumSize();
+        int w = min.width;
+        int h = min.height;
+
+        if (minWidth > w)
+          minWidth = w;
+
+        if (minHeight > h)
+          minHeight = h;
+      }
+    }
+
+    return new Dimension(minWidth, minHeight);
+  }
+
+  public static Insets rotate(Direction dir, Insets insets) {
+    return dir == Direction.RIGHT ? insets :
+           dir == Direction.DOWN ? new Insets(insets.right, insets.top, insets.left, insets.bottom) :
+           dir == Direction.LEFT ? new Insets(insets.bottom, insets.right, insets.top, insets.left) :
+           new Insets(insets.left, insets.bottom, insets.right, insets.top);
+  }
+
+  public static Insets unrotate(Direction dir, Insets insets) {
+    return dir == Direction.RIGHT ? insets :
+           dir == Direction.DOWN ? new Insets(insets.left, insets.bottom, insets.right, insets.top) :
+           dir == Direction.LEFT ? new Insets(insets.bottom, insets.right, insets.top, insets.left) :
+           new Insets(insets.right, insets.top, insets.left, insets.bottom);
+  }
+
+  public static Dimension add(Dimension dim, Insets insets) {
+    return new Dimension(dim.width + insets.left + insets.right, dim.height + insets.top + insets.bottom);
+  }
+
+  public static Dimension getValidSize(Dimension dim, Component component) {
+    Dimension minSize = component.getMinimumSize();
+    Dimension maxSize = component.getMaximumSize();
+    return new Dimension(Math.max(minSize.width, Math.min(dim.width, maxSize.width)),
+                         Math.max(minSize.height, Math.min(dim.height, maxSize.height)));
+  }
+
+  public static Component getChildContaining(Component parent, Component component) {
+    return component == null ?
+           null : component.getParent() == parent ? component : getChildContaining(parent, component.getParent());
+  }
+
+  public static String getBorderLayoutOrientation(Direction direction) {
+    return direction == Direction.UP ? BorderLayout.NORTH :
+           direction == Direction.DOWN ? BorderLayout.SOUTH :
+           direction == Direction.LEFT ? BorderLayout.WEST :
+           BorderLayout.EAST;
+  }
+
+}
diff --git a/src/net/infonode/gui/layout/StackableLayout.java b/src/net/infonode/gui/layout/StackableLayout.java
new file mode 100644
index 0000000..18677fb
--- /dev/null
+++ b/src/net/infonode/gui/layout/StackableLayout.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: StackableLayout.java,v 1.23 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.layout;
+
+import net.infonode.gui.ComponentUtil;
+
+import java.awt.*;
+
+public class StackableLayout implements LayoutManager2 {
+  private Container container;
+  private Component component;
+  private boolean autoShowFirstComponent = true;
+  private boolean useSelectedComponentSize;
+
+  public StackableLayout(Container container) {
+    this.container = container;
+  }
+
+  public boolean usesSelectedComponentSize() {
+    return useSelectedComponentSize;
+  }
+
+  public boolean isAutoShowFirstComponent() {
+    return autoShowFirstComponent;
+  }
+
+  public void setAutoShowFirstComponent(boolean autoShowFirstComponent) {
+    this.autoShowFirstComponent = autoShowFirstComponent;
+  }
+
+  public void setUseSelectedComponentSize(boolean useSelectedComponentSize) {
+    if (this.useSelectedComponentSize != useSelectedComponentSize) {
+      this.useSelectedComponentSize = useSelectedComponentSize;
+      ComponentUtil.validate(container);
+      /*if (container instanceof JComponent)
+        ((JComponent)container).revalidate();
+      else
+        container.validate();*/
+    }
+  }
+
+  public Dimension maximumLayoutSize(Container target) {
+    return LayoutUtil.add(LayoutUtil.getMinMaximumSize(target.getComponents()), target.getInsets());
+  }
+
+  public void invalidateLayout(Container target) {
+  }
+
+  public float getLayoutAlignmentY(Container target) {
+    return 0;
+  }
+
+  public float getLayoutAlignmentX(Container target) {
+    return 0;
+  }
+
+  public void addLayoutComponent(Component comp, Object constraints) {
+    comp.setVisible(autoShowFirstComponent && comp.getParent().getComponentCount() == 1);
+
+    if (comp.isVisible()) {
+      component = comp;
+    }
+  }
+
+  public void addLayoutComponent(String name, Component comp) {
+    addLayoutComponent(comp, null);
+  }
+
+  public void removeLayoutComponent(Component comp) {
+    if (comp == component)
+      component = null;
+
+    comp.setVisible(true);
+  }
+
+  public Dimension preferredLayoutSize(Container parent) {
+    return LayoutUtil.add(useSelectedComponentSize ?
+                          component == null ? new Dimension(0, 0) : component.getPreferredSize() :
+                          LayoutUtil.getMaxPreferredSize(parent.getComponents()), parent.getInsets());
+  }
+
+  public Dimension minimumLayoutSize(Container parent) {
+    return LayoutUtil.add(LayoutUtil.getMaxMinimumSize(parent.getComponents()), parent.getInsets());
+  }
+
+  public void layoutContainer(Container parent) {
+    Component[] c = parent.getComponents();
+    Insets parentInsets = parent.getInsets();
+    Dimension size = LayoutUtil.getInteriorSize(parent);
+
+    for (int i = 0; i < c.length; i++) {
+      c[i].setBounds(parentInsets.left, parentInsets.top, size.width, size.height);
+    }
+  }
+
+  public Component getVisibleComponent() {
+    return component;
+  }
+
+  public void showComponent(Component c) {
+    final Component oldComponent = component;
+
+    if (oldComponent == c)
+      return;
+
+    component = c;
+
+    boolean hasFocus = oldComponent != null &&
+                       LayoutUtil.isDescendingFrom(
+                           KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(),
+                           oldComponent);
+
+    if (oldComponent != null)
+      oldComponent.setVisible(false);
+
+    if (component != null) {
+      component.setVisible(true);
+
+      if (hasFocus)
+      //component.requestFocusInWindow();
+        ComponentUtil.smartRequestFocus(component);
+    }
+
+    
+/*    if (oldComponent != null) {
+//      oldComponent.setVisible(false);
+      SwingUtilities.invokeLater(new Runnable() {
+        public void run() {
+          if (oldComponent.getParent() == container && oldComponent != component) {
+            oldComponent.setVisible(false);
+          }
+
+//          FocusUtil.unblockFocusChanges();
+        }
+      });
+    }*/
+  }
+
+}
diff --git a/src/net/infonode/gui/layout/StretchLayout.java b/src/net/infonode/gui/layout/StretchLayout.java
new file mode 100644
index 0000000..71b654a
--- /dev/null
+++ b/src/net/infonode/gui/layout/StretchLayout.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: StretchLayout.java,v 1.7 2005/02/16 11:28:12 jesper Exp $
+package net.infonode.gui.layout;
+
+import java.awt.*;
+
+public class StretchLayout implements LayoutManager {
+  public static final StretchLayout BOTH = new StretchLayout();
+
+  private boolean horizontal;
+  private boolean vertical;
+
+  public StretchLayout() {
+    this(true, true);
+  }
+
+  public StretchLayout(boolean horizontal, boolean vertical) {
+    this.horizontal = horizontal;
+    this.vertical = vertical;
+  }
+
+  public void addLayoutComponent(String name, Component comp) {
+  }
+
+  public void layoutContainer(Container parent) {
+    Dimension innerSize = LayoutUtil.getInteriorSize(parent);
+    Insets insets = parent.getInsets();
+    Component[] components = LayoutUtil.getVisibleChildren(parent);
+
+    for (int i = 0; i < components.length; i++) {
+      Dimension size = new Dimension(horizontal ? innerSize.width : components[i].getPreferredSize().width,
+                                     vertical ? innerSize.height : components[i].getPreferredSize().height);
+      components[i].setBounds((int) (insets.left + (innerSize.width - size.width) * components[i].getAlignmentX()),
+                              (int) (insets.top + (innerSize.height - size.height) * components[i].getAlignmentY()),
+                              size.width,
+                              size.height);
+    }
+  }
+
+  public Dimension minimumLayoutSize(Container parent) {
+    return LayoutUtil.add(LayoutUtil.getMaxMinimumSize(LayoutUtil.getVisibleChildren(parent)), parent.getInsets());
+  }
+
+  public Dimension preferredLayoutSize(Container parent) {
+    return LayoutUtil.add(LayoutUtil.getMaxPreferredSize(LayoutUtil.getVisibleChildren(parent)), parent.getInsets());
+  }
+
+  public void removeLayoutComponent(Component comp) {
+  }
+}
diff --git a/src/net/infonode/gui/menu/MenuUtil.java b/src/net/infonode/gui/menu/MenuUtil.java
new file mode 100644
index 0000000..3cc1f5e
--- /dev/null
+++ b/src/net/infonode/gui/menu/MenuUtil.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: MenuUtil.java,v 1.3 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui.menu;
+
+import net.infonode.gui.icon.IconUtil;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class MenuUtil {
+  private MenuUtil() {
+  }
+
+  public static void optimizeSeparators(JPopupMenu menu) {
+    boolean lastSeparator = true;
+
+    for (int i = 0; i < menu.getComponentCount();) {
+      if (menu.getComponent(i).isVisible() && menu.getComponent(i) instanceof JMenu)
+        optimizeSeparators(((JMenu) menu.getComponent(i)).getPopupMenu());
+
+      boolean separator = menu.getComponent(i) instanceof JPopupMenu.Separator;
+
+      if (lastSeparator && separator)
+        menu.remove(i);
+      else
+        i++;
+
+      lastSeparator = separator;
+    }
+
+    if (menu.getComponentCount() > 0 &&
+        menu.getComponent(menu.getComponentCount() - 1) instanceof JPopupMenu.Separator)
+      menu.remove(menu.getComponentCount() - 1);
+  }
+
+  public static void align(MenuElement menu) {
+    MenuElement[] children = menu.getSubElements();
+    final int maxWidth = IconUtil.getMaxIconWidth(children);
+
+    for (int i = 0; i < children.length; i++) {
+      if (children[i] instanceof AbstractButton) {
+        AbstractButton b = (AbstractButton) children[i];
+        final Icon icon = b.getIcon();
+        b.setIcon(new Icon() {
+          public int getIconHeight() {
+            return icon == null ? 1 : icon.getIconHeight();
+          }
+
+          public int getIconWidth() {
+            return maxWidth;
+          }
+
+          public void paintIcon(Component c, Graphics g, int x, int y) {
+            if (icon != null)
+              icon.paintIcon(c, g, x, y);
+          }
+        });
+      }
+
+      align(children[i]);
+    }
+  }
+
+}
diff --git a/src/net/infonode/gui/mouse/MouseButtonListener.java b/src/net/infonode/gui/mouse/MouseButtonListener.java
new file mode 100644
index 0000000..02d5645
--- /dev/null
+++ b/src/net/infonode/gui/mouse/MouseButtonListener.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: MouseButtonListener.java,v 1.3 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui.mouse;
+
+import java.awt.event.MouseEvent;
+
+/**
+ * A listener for mouse button events, ie press, release and click.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @since IDW 1.3.0
+ */
+public interface MouseButtonListener {
+  /**
+   * A mouse button event occured.
+   *
+   * @param event the mouse button event
+   */
+  void mouseButtonEvent(MouseEvent event);
+}
diff --git a/src/net/infonode/gui/mouse/package.html b/src/net/infonode/gui/mouse/package.html
new file mode 100644
index 0000000..1161217
--- /dev/null
+++ b/src/net/infonode/gui/mouse/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Mouse related classes.
+</body>
+</html>
diff --git a/src/net/infonode/gui/panel/BaseContainer.java b/src/net/infonode/gui/panel/BaseContainer.java
new file mode 100644
index 0000000..8f1509f
--- /dev/null
+++ b/src/net/infonode/gui/panel/BaseContainer.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BaseContainer.java,v 1.13 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.panel;
+
+import javax.swing.*;
+import javax.swing.plaf.PanelUI;
+import java.awt.*;
+
+public class BaseContainer extends JPanel {
+  private Color foreground;
+
+  private Color background;
+
+  private Font font;
+
+  private Color overridedBackground;
+
+  private Color overridedForeground;
+
+  private Font overridedFont;
+
+  private boolean forcedOpaque = true;
+
+  private boolean opaque = true;
+
+  private static PanelUI UI = new PanelUI() {
+  };
+
+  public BaseContainer() {
+    this(true);
+  }
+
+  public BaseContainer(boolean opaque) {
+    this(opaque, new BorderLayout());
+  }
+
+  public BaseContainer(LayoutManager l) {
+    this(true, l);
+  }
+
+  public BaseContainer(final boolean opaque, LayoutManager l) {
+    super(l);
+
+    this.forcedOpaque = opaque;
+
+    updateOpaque();
+  }
+
+  public void setUI(PanelUI ui) {
+    Color oBackground = overridedBackground;
+    Color oForeground = overridedForeground;
+    Font oFont = overridedFont;
+
+    oBackground = null;
+    oForeground = null;
+    oFont = null;
+
+    setBackground(null);
+    setForeground(null);
+    setFont(null);
+
+    super.setUI(ui);
+
+    background = getBackground();
+    foreground = getForeground();
+    font = getFont();
+
+    overridedBackground = oBackground;
+    overridedForeground = oForeground;
+    overridedFont = oFont;
+
+    if (!forcedOpaque)
+      super.setUI(UI);
+
+    updateBackground();
+    updateForeground();
+    updateFont();
+  }
+
+  void setForcedOpaque(final boolean forcedOpaque) {
+    if (this.forcedOpaque != forcedOpaque) {
+      this.forcedOpaque = forcedOpaque;
+
+      updateUI();
+      updateOpaque();
+    }
+  }
+
+  // Overrided
+
+  public void setOpaque(boolean opaque) {
+    this.opaque = opaque;
+
+    updateOpaque();
+  }
+
+  protected void paintComponent(Graphics g) {
+    if (forcedOpaque)
+      super.paintComponent(g);
+  }
+
+  public void setForeground(Color fg) {
+    this.foreground = fg;
+
+    updateForeground();
+  }
+
+  public void setBackground(Color bg) {
+    this.background = bg;
+
+    updateBackground();
+  }
+
+  public void setFont(Font font) {
+    this.font = font;
+
+    updateFont();
+  }
+
+  void setOverridedForeround(Color fg) {
+    this.overridedForeground = fg;
+
+    updateForeground();
+  }
+
+  void setOverridedBackground(Color bg) {
+    this.overridedBackground = bg;
+
+    updateBackground();
+  }
+
+  void setOverrideFont(Font font) {
+    this.overridedFont = font;
+
+    updateFont();
+  }
+
+  private void updateBackground() {
+    super.setBackground(overridedBackground == null ? background : overridedBackground);
+  }
+
+  private void updateForeground() {
+    super.setForeground(overridedForeground == null ? foreground : overridedForeground);
+  }
+
+  private void updateFont() {
+    super.setFont(overridedFont == null ? font : overridedFont);
+  }
+
+  private void updateOpaque() {
+    super.setOpaque(forcedOpaque ? opaque : forcedOpaque);
+  }
+}
diff --git a/src/net/infonode/gui/panel/BaseContainerUtil.java b/src/net/infonode/gui/panel/BaseContainerUtil.java
new file mode 100644
index 0000000..6768a9b
--- /dev/null
+++ b/src/net/infonode/gui/panel/BaseContainerUtil.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BaseContainerUtil.java,v 1.3 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.panel;
+
+import java.awt.*;
+
+public class BaseContainerUtil {
+
+  public static void setForcedOpaque(BaseContainer c, boolean opaque) {
+    c.setForcedOpaque(opaque);
+  }
+
+  public static void setOverridedBackground(BaseContainer c, Color color) {
+    c.setOverridedBackground(color);
+  }
+
+  public static void setOverridedForeground(BaseContainer c, Color color) {
+    c.setOverridedForeround(color);
+  }
+
+  public static void setOverridedFont(BaseContainer c, Font font) {
+    c.setOverrideFont(font);
+  }
+}
diff --git a/src/net/infonode/gui/panel/BasePanel.java b/src/net/infonode/gui/panel/BasePanel.java
new file mode 100644
index 0000000..88d18ff
--- /dev/null
+++ b/src/net/infonode/gui/panel/BasePanel.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BasePanel.java,v 1.6 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.panel;
+
+import java.awt.*;
+
+/**
+ * A transparent panel with {@link java.awt.BorderLayout}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class BasePanel extends BaseContainer {
+  private Component comp;
+
+  protected BasePanel() {
+    setForcedOpaque(false);
+  }
+
+  protected void setComponent(Component c) {
+    if (comp != null)
+      remove(comp);
+
+    if (c != null) {
+      add(c, BorderLayout.CENTER);
+      revalidate();
+      //c.repaint();
+    }
+
+    comp = c;
+  }
+
+  protected void setSouthComponent(Component c) {
+    add(c, BorderLayout.SOUTH);
+    revalidate();
+  }
+
+}
diff --git a/src/net/infonode/gui/panel/DirectionPanel.java b/src/net/infonode/gui/panel/DirectionPanel.java
new file mode 100644
index 0000000..d069ec9
--- /dev/null
+++ b/src/net/infonode/gui/panel/DirectionPanel.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DirectionPanel.java,v 1.10 2005/12/04 13:46:03 jesper Exp $
+package net.infonode.gui.panel;
+
+import net.infonode.gui.layout.DirectionLayout;
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ */
+public class DirectionPanel extends BaseContainer {
+  public DirectionPanel() {
+    this(Direction.RIGHT, 0);
+  }
+
+  public DirectionPanel(Direction direction, int spacing) {
+    super(new DirectionLayout(direction, spacing));
+    setForcedOpaque(false);
+  }
+
+  public DirectionPanel(Component[] components) {
+    this(Direction.RIGHT, components);
+  }
+
+  public DirectionPanel(int spacing, Component[] components) {
+    this(Direction.RIGHT, spacing, components);
+  }
+
+  public DirectionPanel(Direction direction, Component[] components) {
+    this(direction, 0, components);
+  }
+
+  public DirectionPanel(Direction direction, int spacing, Component[] components) {
+    this(direction, spacing);
+
+    for (int i = 0; i < components.length; i++)
+      add(components[i]);
+  }
+
+  public void setDirection(Direction direction) {
+    ((DirectionLayout) getLayout()).setDirection(direction);
+  }
+}
diff --git a/src/net/infonode/gui/panel/ResizablePanel.java b/src/net/infonode/gui/panel/ResizablePanel.java
new file mode 100644
index 0000000..f733fed
--- /dev/null
+++ b/src/net/infonode/gui/panel/ResizablePanel.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ResizablePanel.java,v 1.19 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui.panel;
+
+import net.infonode.gui.CursorManager;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.19 $
+ */
+public class ResizablePanel extends BaseContainer {
+  private Direction direction;
+  private int resizeWidth = 4;
+  private boolean cursorChanged;
+  private int offset = -1;
+  private boolean mouseInside;
+  private boolean heavyWeight = true;
+  private boolean continuousLayout = false;
+  private Component dragIndicator;
+  private JComponent layeredPane;
+  private JComponent innerArea;
+  private Dimension lastSize;
+  private int dragIndicatorThickness = 4;
+  private Component comp;
+
+  public ResizablePanel(Direction _direction) {
+    this(false, _direction, null);
+  }
+
+  public ResizablePanel(boolean useHeavyWeightDragIndicator, Direction _direction, Component mouseListenComponent) {
+    super(new BorderLayout());
+    this.heavyWeight = useHeavyWeightDragIndicator;
+    this.direction = _direction;
+
+    if (heavyWeight) {
+      dragIndicator = new Canvas();
+    }
+    else {
+      dragIndicator = new BaseContainer();
+    }
+    setDragIndicatorColor(null);
+    if (mouseListenComponent == null)
+      mouseListenComponent = this;
+
+    mouseListenComponent.addMouseListener(new MouseAdapter() {
+      public void mouseExited(MouseEvent e) {
+        if (offset == -1)
+          resetCursor();
+
+        mouseInside = false;
+      }
+
+      public void mouseEntered(MouseEvent e) {
+        mouseInside = true;
+      }
+
+      public void mousePressed(MouseEvent e) {
+        //if (MouseEventCoalesceManager.getInstance().isPressedAllowed(e)) {
+        if (!continuousLayout && layeredPane != null) {
+          if (layeredPane instanceof JLayeredPane)
+            layeredPane.add(dragIndicator, JLayeredPane.DRAG_LAYER);
+          else
+            layeredPane.add(dragIndicator, 0);
+
+          layeredPane.repaint();
+          updateDragIndicator(e);
+        }
+        if (cursorChanged) {
+          offset = direction == Direction.LEFT ? e.getPoint().x : direction == Direction.RIGHT ? getWidth() - e.getPoint()
+              .x : direction == Direction.UP ? e.getPoint().y : getHeight()
+                                                                - e.getPoint().y;
+        }
+        //}
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        //if (MouseEventCoalesceManager.getInstance().isReleasedAllowed(e)) {
+        if (!continuousLayout && layeredPane != null) {
+          layeredPane.remove(dragIndicator);
+          layeredPane.repaint();
+        }
+        offset = -1;
+        checkCursor(e.getPoint());
+
+        if (!continuousLayout && lastSize != null) {
+          setPreferredSize(lastSize);
+          revalidate();
+        }
+
+        lastSize = null;
+        //}
+      }
+    });
+
+    mouseListenComponent.addMouseMotionListener(new MouseMotionAdapter() {
+      public void mouseMoved(MouseEvent e) {
+        checkCursor(e.getPoint());
+      }
+
+      public void mouseDragged(MouseEvent e) {
+        if (offset != -1) {// && MouseEventCoalesceManager.getInstance().isDraggedAllowed(e)) {
+          int size = direction.isHorizontal() ?
+                     (direction == Direction.LEFT ? getWidth() - e.getPoint().x + offset : e.getPoint().x + offset) :
+                     (direction == Direction.UP ? getHeight() - e.getPoint().y + offset : e.getPoint().y + offset);
+          lastSize = getBoundedSize(size);
+
+          if (continuousLayout) {
+            setPreferredSize(lastSize);
+            revalidate();
+          }
+          else {
+            updateDragIndicator(e);
+          }
+        }
+      }
+    });
+  }
+
+  public void setComponent(Component c) {
+    if (comp != null)
+      remove(comp);
+
+    if (c != null) {
+      add(c, BorderLayout.CENTER);
+      //c.repaint();
+      revalidate();
+    }
+
+    comp = c;
+  }
+
+  public void setDragIndicatorColor(Color color) {
+    dragIndicator.setBackground(color == null ? Color.DARK_GRAY : color);
+  }
+
+  public void setLayeredPane(JComponent layeredPane) {
+    this.layeredPane = layeredPane;
+    if (innerArea == null)
+      innerArea = layeredPane;
+  }
+
+  public void setInnerArea(JComponent innerArea) {
+    if (innerArea == null)
+      innerArea = layeredPane;
+    else
+      this.innerArea = innerArea;
+  }
+
+  public boolean isContinuousLayout() {
+    return continuousLayout;
+  }
+
+  public void setContinuousLayout(boolean continuousLayout) {
+    this.continuousLayout = continuousLayout;
+  }
+
+  public Dimension getPreferredSize() {
+    Dimension d = super.getPreferredSize();
+    return getBoundedSize(direction.isHorizontal() ? d.width : d.height);
+  }
+
+  private void updateDragIndicator(MouseEvent e) {
+    if (layeredPane != null) {
+      Point p = SwingUtilities.convertPoint((Component) e.getSource(), e.getPoint(), layeredPane);
+      Point p2 = SwingUtilities.convertPoint(this.getParent(), getLocation(), layeredPane);
+      Dimension size = innerArea.getSize();
+      Dimension minimumSize = getMinimumSize();
+      Point offset = SwingUtilities.convertPoint(innerArea, 0, 0, layeredPane);
+
+      if (direction.isHorizontal()) {
+        int x = 0;
+        if (direction == Direction.LEFT)
+          x = Math.min(Math.max(offset.x, p.x), offset.x + size.width - minimumSize.width);
+        else
+          x = Math.min(Math.max(offset.x + minimumSize.width, p.x), offset.x + size.width) - dragIndicatorThickness;
+
+        dragIndicator.setBounds(x, p2.y, dragIndicatorThickness, getHeight());
+      }
+      else {
+        int y = 0;
+        if (direction == Direction.UP)
+          y = Math.min(Math.max(offset.y, p.y), offset.y + size.height - minimumSize.height);
+        else
+          y = Math.min(Math.max(offset.y + minimumSize.height, p.y), offset.y + size.height) - dragIndicatorThickness;
+
+        dragIndicator.setBounds(p2.x, y, getWidth(), dragIndicatorThickness);
+      }
+    }
+  }
+
+  private Dimension getBoundedSize(int size) {
+    if (direction.isHorizontal()) {
+      return new Dimension(Math.max(getMinimumSize().width, Math.min(size, getMaximumSize().width)), 0);
+    }
+    else {
+      return new Dimension(0, Math.max(getMinimumSize().height, Math.min(size, getMaximumSize().height)));
+    }
+  }
+
+  public void setResizeWidth(int width) {
+    this.resizeWidth = width;
+  }
+
+  public int getResizeWidth() {
+    return resizeWidth;
+  }
+
+  private void checkCursor(Point point) {
+    if (offset != -1)
+      return;
+
+    int dist = direction == Direction.UP ? point.y :
+               direction == Direction.DOWN ? getHeight() - point.y :
+               direction == Direction.LEFT ? point.x :
+               getWidth() - point.x;
+
+    if (dist >= 0 && dist < resizeWidth && mouseInside) {
+      if (!cursorChanged) {
+        cursorChanged = true;
+        CursorManager.setGlobalCursor(getRootPane(),
+                                      new Cursor(direction == Direction.LEFT ? Cursor.W_RESIZE_CURSOR :
+                                                 direction == Direction.RIGHT ? Cursor.E_RESIZE_CURSOR :
+                                                 direction == Direction.UP ? Cursor.N_RESIZE_CURSOR :
+                                                 Cursor.S_RESIZE_CURSOR));
+      }
+    }
+    else
+      resetCursor();
+  }
+
+  private void resetCursor() {
+    CursorManager.resetGlobalCursor(getRootPane());
+    cursorChanged = false;
+  }
+
+  public Direction getDirection() {
+    return direction;
+  }
+
+  public void setVisible(boolean aFlag) {
+    super.setVisible(aFlag);
+  }
+}
diff --git a/src/net/infonode/gui/panel/SimplePanel.java b/src/net/infonode/gui/panel/SimplePanel.java
new file mode 100644
index 0000000..77408da
--- /dev/null
+++ b/src/net/infonode/gui/panel/SimplePanel.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SimplePanel.java,v 1.11 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui.panel;
+
+import javax.swing.border.Border;
+import java.awt.*;
+
+public class SimplePanel extends BaseContainer {
+  private Component comp;
+
+  public SimplePanel() {
+    this(new BorderLayout());
+  }
+
+  public SimplePanel(Border border) {
+    this();
+    setBorder(border);
+  }
+
+  public SimplePanel(Border border, Component comp) {
+    this(comp);
+    setBorder(border);
+  }
+
+  public SimplePanel(LayoutManager layoutManager) {
+    super(false, layoutManager);
+  }
+
+  public SimplePanel(Component c) {
+    this();
+    setComponent(c);
+  }
+
+  public SimplePanel(Component c, Component northComponent) {
+    this(c);
+    add(northComponent, BorderLayout.NORTH);
+  }
+
+  public SimplePanel(Border border, Component c, Component northComponent) {
+    this(border, c);
+    add(northComponent, BorderLayout.NORTH);
+  }
+
+  public void setComponent(Component c) {
+    if (comp != null)
+      remove(comp);
+
+    if (c != null) {
+      add(c, BorderLayout.CENTER);
+      // c.repaint();
+      revalidate();
+    }
+
+    comp = c;
+  }
+
+  public void setSouthComponent(Component c) {
+    add(c, BorderLayout.SOUTH);
+    revalidate();
+  }
+}
diff --git a/src/net/infonode/gui/panel/package.html b/src/net/infonode/gui/panel/package.html
new file mode 100644
index 0000000..ec5cf00
--- /dev/null
+++ b/src/net/infonode/gui/panel/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Panel classes.
+</body>
+</html>
diff --git a/src/net/infonode/gui/shaped/ShapedUtil.java b/src/net/infonode/gui/shaped/ShapedUtil.java
new file mode 100644
index 0000000..685c7b2
--- /dev/null
+++ b/src/net/infonode/gui/shaped/ShapedUtil.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ShapedUtil.java,v 1.5 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui.shaped;
+
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.RectangleUtil;
+import net.infonode.gui.shaped.panel.ShapedPanel;
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class ShapedUtil {
+  private ShapedUtil() {
+  }
+
+  public static Direction getDirection(Component c) {
+    return c instanceof ShapedPanel ? ((ShapedPanel) c).getDirection() : Direction.RIGHT;
+  }
+
+  public static Insets transformInsets(Component c, Insets insets) {
+    return InsetsUtil.rotate(getDirection(c), flipInsets(c, insets));
+  }
+
+  public static Insets flipInsets(Component c, Insets i) {
+    if (c instanceof ShapedPanel) {
+      if (((ShapedPanel) c).isHorizontalFlip())
+        i = InsetsUtil.flipHorizontal(i);
+      if (((ShapedPanel) c).isVerticalFlip())
+        i = InsetsUtil.flipVertical(i);
+    }
+
+    return i;
+  }
+
+  public static void rotateCW(Polygon polygon, int height) {
+    for (int i = 0; i < polygon.npoints; i++) {
+      int tmp = polygon.ypoints[i];
+      polygon.ypoints[i] = polygon.xpoints[i];
+      polygon.xpoints[i] = height - 1 - tmp;
+    }
+  }
+
+  public static void rotate(Polygon polygon, Direction d, int width, int height) {
+    if (d == Direction.UP) {
+      rotateCW(polygon, height);
+      rotateCW(polygon, width);
+      rotateCW(polygon, height);
+    }
+    else if (d == Direction.LEFT) {
+      rotateCW(polygon, height);
+      rotateCW(polygon, width);
+    }
+    else if (d == Direction.DOWN) {
+      rotateCW(polygon, height);
+    }
+  }
+
+  public static Rectangle transform(Component c, Rectangle rect) {
+    if (c instanceof ShapedPanel) {
+      ShapedPanel sp = (ShapedPanel) c;
+      return RectangleUtil.transform(rect,
+                                     sp.getDirection(),
+                                     sp.isHorizontalFlip(),
+                                     sp.isVerticalFlip(),
+                                     c.getWidth(),
+                                     c.getHeight());
+    }
+    else
+      return rect;
+  }
+
+  public static Dimension transform(Component c, Dimension dim) {
+    return getDirection(c).isHorizontal() ? dim : new Dimension(dim.height, dim.width);
+  }
+
+  public static int getWidth(Component c, int width, int height) {
+    return getDirection(c).isHorizontal() ? width : height;
+  }
+
+  public static int getHeight(Component c, int width, int height) {
+    return getDirection(c).isHorizontal() ? height : width;
+  }
+
+}
diff --git a/src/net/infonode/gui/shaped/border/AbstractPolygonBorder.java b/src/net/infonode/gui/shaped/border/AbstractPolygonBorder.java
new file mode 100644
index 0000000..ede07cb
--- /dev/null
+++ b/src/net/infonode/gui/shaped/border/AbstractPolygonBorder.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractPolygonBorder.java,v 1.18 2005/12/04 13:46:04 jesper Exp $
+
+package net.infonode.gui.shaped.border;
+
+import net.infonode.gui.GraphicsUtil;
+import net.infonode.gui.HighlightPainter;
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.colorprovider.BackgroundPainterColorProvider;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.FixedColorProvider;
+import net.infonode.gui.shaped.ShapedUtil;
+import net.infonode.gui.shaped.panel.ShapedPanel;
+
+import java.awt.*;
+
+/**
+ * @author johan
+ */
+abstract public class AbstractPolygonBorder extends AbstractShapedBorder {
+  private static final long serialVersionUID = 1;
+
+  private static final Insets HIGHLIGHT_INSETS = new Insets(1, 1, 0, 0);
+  private ColorProvider lineColor;
+  private ColorProvider highlightColor = new FixedColorProvider(new Color(255, 255, 255));
+  private ColorProvider middleColor;
+  private ColorProvider shadowColor;
+
+  protected AbstractPolygonBorder(ColorProvider lineColor) {
+    this(lineColor, FixedColorProvider.WHITE);
+  }
+
+  protected AbstractPolygonBorder(ColorProvider lineColor, ColorProvider highlightColor) {
+    this(lineColor, highlightColor, BackgroundPainterColorProvider.INSTANCE, null);
+  }
+
+  protected AbstractPolygonBorder(ColorProvider lineColor,
+                                  ColorProvider highlightColor,
+                                  ColorProvider middleColor,
+                                  ColorProvider shadowColor) {
+    this.lineColor = lineColor;
+    this.highlightColor = highlightColor;
+    this.middleColor = middleColor;
+    this.shadowColor = shadowColor;
+  }
+
+  public Shape getShape(Component c, int x, int y, int width, int height) {
+    int w = ShapedUtil.getWidth(c, width, height);
+    int h = ShapedUtil.getHeight(c, width, height);
+    Polygon polygon = getPolygon(c, x, y, w, h);
+
+    //printPoints(polygon);
+
+    //System.out.println("Polygon: width=" + w + " height=" + h);
+
+    return polygon;
+  }
+
+  public boolean isBorderOpaque() {
+    return false;
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    Shape clip = g.getClip();
+    g.clipRect(x, y, width, height);
+
+    try {
+      int w = ShapedUtil.getWidth(c, width, height);
+      height = ShapedUtil.getHeight(c, width, height);
+      width = w;
+
+      Polygon polygon = getPolygon(c, x, y, width, height);
+      Graphics2D g2 = (Graphics2D) g;
+
+      if (highlightColor != null) {
+        paintHighlight(c, g2, polygon, width, height);
+      }
+
+      if (lineColor != null) {
+        g.setColor(lineColor.getColor());
+        paintPolygon(c, g2, polygon, width, height);
+      }
+    }
+    finally {
+      g.setClip(clip);
+    }
+  }
+
+  public Insets getBorderInsets(Component c) {
+    Insets insets = getShapedBorderInsets(c);
+    insets = ShapedUtil.transformInsets(c, insets);
+    return highlightColor != null ? InsetsUtil.add(getShapedBorderHighlightInsets(c), insets) : insets;
+  }
+
+  protected Insets getShapedBorderInsets(Component c) {
+    return new Insets(0, 0, 0, 0);
+  }
+
+  protected Insets getShapedBorderHighlightInsets(Component c) {
+    return HIGHLIGHT_INSETS;
+  }
+
+  protected Polygon createPolygon(Component c, int width, int height) {
+    return new Polygon();
+  }
+
+  protected void paintPolygon(Component c, Graphics2D g, Polygon polygon, int width, int height) {
+    int i = 0;
+
+    while (i < polygon.npoints) {
+      if (lineIsDrawn(i, polygon)) {
+        int ni = (i + 1) % polygon.npoints;
+        GraphicsUtil.drawOptimizedLine(g, polygon.xpoints[i],
+                                       polygon.ypoints[i],
+                                       polygon.xpoints[ni],
+                                       polygon.ypoints[ni]);
+      }
+
+      i++;
+    }
+  }
+
+  protected void paintHighlight(Component c, Graphics2D g, Polygon polygon, int width, int height) {
+    Color c1 = highlightColor == null ? null : highlightColor.getColor(c);
+    Color c2 = middleColor.getColor(c);
+    Color c3 = shadowColor == null ? null : shadowColor.getColor(c);
+
+    boolean clockWise = isPointsClockwise(c);
+
+    for (int i = 0; i < polygon.npoints; i++) {
+      int ni = (i + 1) % polygon.npoints;
+
+      if (lineIsDrawn(i, polygon)) {
+        HighlightPainter.drawLine(g,
+                                  polygon.xpoints[i],
+                                  polygon.ypoints[i],
+                                  polygon.xpoints[ni],
+                                  polygon.ypoints[ni],
+                                  clockWise,
+                                  true,
+                                  c1, c2, c3);
+      }
+    }
+  }
+
+  protected boolean lineIsDrawn(int index, Polygon polygon) {
+    return true;
+  }
+
+/*  protected int getWidth(Component c) {
+    Insets i = getOuterInsets(c);
+    Direction d = ShapedUtil.getDirection(c);
+    if (d == Direction.UP || d == Direction.DOWN)
+      return c.getHeight() - i.top - i.bottom;
+
+    return c.getWidth() - i.left - i.right;
+  }
+
+  protected int getHeight(Component c) {
+    Insets i = getOuterInsets(c);
+    Direction d = ShapedUtil.getDirection(c);
+    if (d == Direction.UP || d == Direction.DOWN)
+      return c.getWidth() - i.left - i.right;
+
+    return c.getHeight() - i.top - i.bottom;
+  }
+*/
+  protected boolean isHighlightable(int deltaX, int deltaY) {
+    return deltaX > deltaY;
+  }
+
+  protected boolean isPointsClockwise(Component c) {
+    if (c instanceof ShapedPanel)
+      return !(((ShapedPanel) c).isHorizontalFlip() ^ ((ShapedPanel) c).isVerticalFlip());
+
+    return true;
+  }
+
+  protected int getHighlightOffsetX(int deltaX, int deltaY) {
+    return deltaY - deltaX > 0 ? (deltaX + deltaY > 0 ? -1 : 0) : (deltaX + deltaY > 0 ? 0 : 1); //-deltaY > deltaX ? 1 : 0;
+  }
+
+  protected int getHighlightOffsetY(int deltaX, int deltaY) {
+    return deltaY - deltaX > 0 ? (deltaX + deltaY > 0 ? 0 : -1) : (deltaX + deltaY > 0 ? 1 : 0); //-deltaY > deltaX ? 1 : 0;
+  }
+
+  protected void setPoint(Polygon polygon, int x, int y) {
+    polygon.xpoints[polygon.npoints] = x;
+    polygon.ypoints[polygon.npoints] = y;
+    polygon.npoints++;
+  }
+
+  private Polygon getPolygon(Component c, int x, int y, int width, int height) {
+    Polygon polygon = createPolygon(c, width, height);
+    flipPolygon(c, polygon, width, height);
+    rotatePolygon(c, polygon, width, height);
+    fixGraphicsOffset(c, polygon, x, y);
+    return polygon;
+  }
+
+  private void flipPolygon(Component c, Polygon polygon, int width, int height) {
+    if (c instanceof ShapedPanel) {
+      if (((ShapedPanel) c).isHorizontalFlip()) {
+        for (int i = 0; i < polygon.npoints; i++)
+          polygon.xpoints[i] = Math.abs(width - polygon.xpoints[i]) - 1;
+      }
+
+      if (((ShapedPanel) c).isVerticalFlip()) {
+        for (int i = 0; i < polygon.npoints; i++)
+          polygon.ypoints[i] = Math.abs(height - polygon.ypoints[i]) - 1;
+      }
+    }
+  }
+
+  private void rotatePolygon(Component c, Polygon polygon, int width, int height) {
+    ShapedUtil.rotate(polygon, ShapedUtil.getDirection(c), width, height);
+  }
+
+  private void fixGraphicsOffset(Component c, Polygon polygon, int x, int y) {
+    for (int i = 0; i < polygon.npoints; i++) {
+      polygon.xpoints[i] += x;
+      polygon.ypoints[i] += y;
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/src/net/infonode/gui/shaped/border/AbstractShapedBorder.java b/src/net/infonode/gui/shaped/border/AbstractShapedBorder.java
new file mode 100644
index 0000000..df7cf20
--- /dev/null
+++ b/src/net/infonode/gui/shaped/border/AbstractShapedBorder.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractShapedBorder.java,v 1.4 2004/11/05 17:53:08 johan Exp $
+package net.infonode.gui.shaped.border;
+
+import java.io.Serializable;
+
+/**
+ * @author $Author: johan $
+ * @version $Revision: 1.4 $
+ */
+abstract public class AbstractShapedBorder implements ShapedBorder, Serializable {
+  private static final long serialVersionUID = 1;
+
+}
diff --git a/src/net/infonode/gui/shaped/border/AbstractShapedBorderWrapper.java b/src/net/infonode/gui/shaped/border/AbstractShapedBorderWrapper.java
new file mode 100644
index 0000000..60a7480
--- /dev/null
+++ b/src/net/infonode/gui/shaped/border/AbstractShapedBorderWrapper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractShapedBorderWrapper.java,v 1.7 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui.shaped.border;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class AbstractShapedBorderWrapper extends AbstractShapedBorder {
+  private static final long serialVersionUID = 1;
+
+  private ShapedBorder border;
+
+  protected AbstractShapedBorderWrapper(ShapedBorder border) {
+    this.border = border;
+  }
+
+  public Shape getShape(Component c, int x, int y, int width, int height) {
+    return border.getShape(c, x, y, width, height);
+  }
+
+  public Insets getBorderInsets(Component c) {
+    return border.getBorderInsets(c);
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    border.paintBorder(c, g, x, y, width, height);
+  }
+
+  public boolean isBorderOpaque() {
+    return border.isBorderOpaque();
+  }
+}
diff --git a/src/net/infonode/gui/shaped/border/FixedInsetsShapedBorder.java b/src/net/infonode/gui/shaped/border/FixedInsetsShapedBorder.java
new file mode 100644
index 0000000..ce9789c
--- /dev/null
+++ b/src/net/infonode/gui/shaped/border/FixedInsetsShapedBorder.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FixedInsetsShapedBorder.java,v 1.6 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui.shaped.border;
+
+import net.infonode.gui.shaped.ShapedUtil;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class FixedInsetsShapedBorder extends AbstractShapedBorderWrapper {
+  private static final long serialVersionUID = 1;
+
+  private Insets insets;
+
+  public FixedInsetsShapedBorder(Insets insets, ShapedBorder border) {
+    super(border);
+    this.insets = insets;
+  }
+
+  public Insets getBorderInsets(Component c) {
+    return ShapedUtil.transformInsets(c, insets);
+  }
+
+}
diff --git a/src/net/infonode/gui/shaped/border/PolygonBorder.java b/src/net/infonode/gui/shaped/border/PolygonBorder.java
new file mode 100644
index 0000000..48d9dac
--- /dev/null
+++ b/src/net/infonode/gui/shaped/border/PolygonBorder.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PolygonBorder.java,v 1.8 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui.shaped.border;
+
+import net.infonode.gui.colorprovider.ColorProvider;
+
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class PolygonBorder extends AbstractPolygonBorder {
+  private static final long serialVersionUID = 1;
+
+  private int[] coords;
+  private float[] widthFactors;
+  private float[] heightFactors;
+
+  public PolygonBorder(ColorProvider lineColor, int[] coords, float[] widthFactors, float[] heightFactors) {
+    super(lineColor);
+    this.coords = coords;
+    this.widthFactors = widthFactors;
+    this.heightFactors = heightFactors;
+  }
+
+  public PolygonBorder(ColorProvider lineColor,
+                       ColorProvider highlightColor,
+                       int[] coords,
+                       float[] widthFactors,
+                       float[] heightFactors) {
+    super(lineColor, highlightColor);
+    this.coords = coords;
+    this.widthFactors = widthFactors;
+    this.heightFactors = heightFactors;
+  }
+
+  public PolygonBorder(ColorProvider lineColor,
+                       ColorProvider highlightColor,
+                       ColorProvider middleColor,
+                       ColorProvider shadowColor,
+                       int[] coords,
+                       float[] widthFactors,
+                       float[] heightFactors) {
+    super(lineColor, highlightColor, middleColor, shadowColor);
+    this.coords = coords;
+    this.widthFactors = widthFactors;
+    this.heightFactors = heightFactors;
+  }
+
+  protected Polygon createPolygon(Component c, int width, int height) {
+    int[] xc = new int[coords.length / 2];
+    int[] yc = new int[coords.length / 2];
+    int n = 0;
+
+    for (int i = 0; i < xc.length; i++) {
+      xc[i] = (int) (widthFactors[n] * width) + (int) (heightFactors[n] * height) + coords[n];//(coords[n] >= 0 ? coords[n] : width + coords[n]);
+      n++;
+      yc[i] = (int) (widthFactors[n] * width) + (int) (heightFactors[n] * height) + coords[n];//(coords[n] >= 0 ? coords[n] : height + coords[n]);
+      n++;
+    }
+
+    return new Polygon(xc, yc, xc.length);
+  }
+
+}
diff --git a/src/net/infonode/gui/shaped/border/RoundedCornerBorder.java b/src/net/infonode/gui/shaped/border/RoundedCornerBorder.java
new file mode 100644
index 0000000..246a910
--- /dev/null
+++ b/src/net/infonode/gui/shaped/border/RoundedCornerBorder.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: RoundedCornerBorder.java,v 1.9 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui.shaped.border;
+
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.FixedColorProvider;
+
+import java.awt.*;
+
+/**
+ * @author johan
+ */
+public class RoundedCornerBorder extends PolygonBorder {
+  private static final long serialVersionUID = 1;
+
+  private static int[][] corner1 = {
+    {0, 0},
+    {0, 1, 1, 0},
+    {0, 2, 2, 0},
+    {0, 4, 1, 3, 1, 2, 2, 1, 3, 1, 4, 0},
+    {0, 5, 1, 4, 1, 3, 3, 1, 4, 1, 5, 0}};
+
+  private static int[][] corner2 = {
+    {-1, 0},
+    {-2, 0, -1, 1},
+    {-3, 0, -1, 2},
+    {-5, 0, -4, 1, -3, 1, -2, 2, -2, 3, -1, 4},
+    {-6, 0, -5, 1, -4, 1, -2, 3, -2, 4, -1, 5}};
+
+  private static int[][] corner3 = {
+    {-1, -1},
+    {-1, -2, -2, -1},
+    {-1, -3, -3, -1},
+    {-1, -5, -2, -4, -2, -3, -3, -2, -4, -2, -5, -1},
+    {-1, -6, -2, -5, -2, -4, -4, -2, -5, -2, -6, -1}};
+
+  private static int[][] corner4 = {
+    {0, -1},
+    {1, -1, 0, -2},
+    {2, -1, 0, -3},
+    {4, -1, 3, -2, 2, -2, 1, -3, 1, -4, 0, -5},
+    {5, -1, 4, -2, 3, -2, 1, -4, 1, -5, 0, -6}};
+
+  private Insets insets;
+
+  public RoundedCornerBorder(ColorProvider lineColor, int cType) {
+    this(lineColor, cType, true, true, true, true);
+  }
+
+  public RoundedCornerBorder(ColorProvider lineColor,
+                             int cType,
+                             boolean drawTop,
+                             boolean drawLeft,
+                             boolean drawBottom,
+                             boolean drawRight) {
+    this(lineColor, FixedColorProvider.WHITE, cType, cType, cType, cType, drawTop, drawLeft, drawBottom, drawRight);
+  }
+
+  public RoundedCornerBorder(ColorProvider lineColor, ColorProvider highlightColor,
+                             int cType1, int cType2, int cType3, int cType4) {
+    this(lineColor, highlightColor, cType1, cType2, cType3, cType4, true, true, true, true);
+  }
+
+  public RoundedCornerBorder(ColorProvider lineColor, ColorProvider highlightColor,
+                             int cType1, int cType2, int cType3, int cType4,
+                             boolean drawTop, boolean drawLeft, boolean drawBottom, boolean drawRight) {
+    super(lineColor,
+          highlightColor,
+          createCoordinates(cType1, cType2, cType3, cType4, drawTop, drawLeft, drawBottom, drawRight),
+          createXScales(cType1, cType2, cType3, cType4),
+          createYScales(cType1, cType2, cType3, cType4));
+
+    insets = new Insets(drawTop ? 1 : 0,
+                        drawLeft ? 1 : 0,
+                        drawBottom ? 1 : 0,
+                        drawRight ? 1 : 0);
+  }
+
+  protected Insets getShapedBorderInsets(Component c) {
+    return insets;
+  }
+
+  private static int[] createCoordinates(int cType1,
+                                         int cType2,
+                                         int cType3,
+                                         int cType4,
+                                         boolean drawTop,
+                                         boolean drawLeft,
+                                         boolean drawBottom,
+                                         boolean drawRight) {
+    int c1[] = corner1[cType1];
+    int c2[] = corner2[cType2];
+    int c3[] = corner3[cType3];
+    int c4[] = corner4[cType4];
+
+    int coords[] = new int[c1.length + c2.length + c3.length + c4.length];
+    int index = 0;
+
+    for (int i = 0; i < c1.length; i++) {
+      coords[index] = drawLeft ? c1[i] : c1[i] - 1;
+      index++;
+      i++;
+      coords[index] = drawTop ? c1[i] : c1[i] - 1;
+      index++;
+    }
+
+    for (int i = 0; i < c2.length; i++) {
+      coords[index] = drawRight ? c2[i] : c2[i] + 1;
+      index++;
+      i++;
+      coords[index] = drawTop ? c2[i] : c2[i] - 1;
+      index++;
+    }
+
+    for (int i = 0; i < c3.length; i++) {
+      coords[index] = drawRight ? c3[i] : c3[i] + 1;
+      index++;
+      i++;
+      coords[index] = drawBottom ? c3[i] : c3[i] + 1;
+      index++;
+    }
+
+    for (int i = 0; i < c4.length; i++) {
+      coords[index] = drawLeft ? c4[i] : c4[i] - 1;
+      index++;
+      i++;
+      coords[index] = drawBottom ? c4[i] : c4[i] + 1;
+      index++;
+    }
+
+    return coords;
+  }
+
+  private static float[] createXScales(int cType1, int cType2, int cType3, int cType4) {
+    int c[][] = new int[4][1];
+    c[0] = corner1[cType1];
+    c[1] = corner2[cType2];
+    c[2] = corner3[cType3];
+    c[3] = corner4[cType4];
+
+    float xscales[] = new float[c[0].length + c[1].length + c[2].length + c[3].length];
+    int index = 0;
+
+    for (int k = 0; k < c.length; k++) {
+      for (int i = 0; i < c[k].length; i++) {
+        xscales[index] = c[k][i] < 0 ? 1 : 0;
+        i++;
+        index += 2;
+      }
+
+    }
+
+    return xscales;
+  }
+
+  private static float[] createYScales(int cType1, int cType2, int cType3, int cType4) {
+    int c[][] = new int[4][1];
+    c[0] = corner1[cType1];
+    c[1] = corner2[cType2];
+    c[2] = corner3[cType3];
+    c[3] = corner4[cType4];
+
+    float yscales[] = new float[c[0].length + c[1].length + c[2].length + c[3].length];
+    int index = 1;
+
+    for (int k = 0; k < c.length; k++) {
+      for (int i = 1; i < c[k].length; i++) {
+        yscales[index] = c[k][i] < 0 ? 1 : 0;
+        i++;
+        index += 2;
+      }
+    }
+
+    return yscales;
+  }
+}
diff --git a/src/net/infonode/gui/shaped/border/ShapedBorder.java b/src/net/infonode/gui/shaped/border/ShapedBorder.java
new file mode 100644
index 0000000..f15f36c
--- /dev/null
+++ b/src/net/infonode/gui/shaped/border/ShapedBorder.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ShapedBorder.java,v 1.9 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.gui.shaped.border;
+
+import javax.swing.border.Border;
+import java.awt.*;
+
+/**
+ * A border that has a {@link Shape}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ */
+public interface ShapedBorder extends Border {
+
+  /**
+   * <p>Gets the shape of this border.</p>
+   *
+   * @param c      the component to create a shape for
+   * @param x      the x offset
+   * @param y      the y offset
+   * @param width  the width
+   * @param height the height
+   * @return the Shape for this border or null if there is no shape and
+   *         the normal rectangle bounds should be used
+   */
+  Shape getShape(Component c, int x, int y, int width, int height);
+}
diff --git a/src/net/infonode/gui/shaped/border/package.html b/src/net/infonode/gui/shaped/border/package.html
new file mode 100644
index 0000000..c1155f9
--- /dev/null
+++ b/src/net/infonode/gui/shaped/border/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Package for shaped borders. A shaped border is a border with a shape that is used by shaped panels for example.
+</body>
+</html>
diff --git a/src/net/infonode/gui/shaped/panel/ShapedPanel.java b/src/net/infonode/gui/shaped/panel/ShapedPanel.java
new file mode 100644
index 0000000..ec6484f
--- /dev/null
+++ b/src/net/infonode/gui/shaped/panel/ShapedPanel.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ShapedPanel.java,v 1.32 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.gui.shaped.panel;
+
+import net.infonode.gui.BackgroundPainter;
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.componentpainter.ComponentPainter;
+import net.infonode.gui.panel.BaseContainer;
+import net.infonode.gui.shaped.border.ShapedBorder;
+import net.infonode.util.Direction;
+
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import java.awt.*;
+
+/**
+ * <p>
+ * A panel that has support for a {@link ComponentPainter} and a {@link ShapedBorder}.
+ * The background of the panel is painted as normal and then the {@link ComponentPainter}
+ * paints the area inside the {@link ShapedBorder} or the complete component area if the
+ * its border isn't a {@link ShapedBorder}.
+ * </p>
+ *
+ * <p>
+ * If a {@link ShapedBorder} is applied to this panel, mouse events etc. are only triggered
+ * for this panel if the point is inside the {@link Shape} of the {@link ShapedBorder}. Child
+ * components of this panel can optionally be clipped using the {@link Shape}.
+ * </p>
+ *
+ * <p>
+ * A {@link ShapedBorder} wrapped inside {@link CompoundBorder}'s will be used by the ShapedPanel,
+ * but a {@link ShapedBorder} wrapped inside other border types can't be found and is hence not
+ * used by the panel.
+ * </p>
+ *
+ * @author johan
+ */
+public class ShapedPanel extends BaseContainer implements BackgroundPainter {
+  private Direction direction = Direction.RIGHT;
+  private boolean horizontalFlip;
+  private boolean verticalFlip;
+  private boolean clipChildren;
+  private ComponentPainter painter;
+  private ShapedBorder shapedBorder;
+  private Insets shapedInsets;
+
+  public ShapedPanel() {
+    super();
+  }
+
+  public ShapedPanel(LayoutManager l) {
+    super(l);
+  }
+
+  public ShapedPanel(ComponentPainter painter) {
+    this();
+    this.painter = painter;
+  }
+
+  public ShapedPanel(ComponentPainter painter, Border border) {
+    this(painter);
+    setBorder(border);
+  }
+
+  public ShapedPanel(Component component) {
+    this();
+    add(component, BorderLayout.CENTER);
+  }
+
+  public Shape getShape() {
+    ShapedBorder b = getShapedBorder();
+    return b == null ? null : b.getShape(this,
+                                         shapedInsets.left,
+                                         shapedInsets.top,
+                                         getWidth() - shapedInsets.left - shapedInsets.right,
+                                         getHeight() - shapedInsets.top - shapedInsets.bottom);
+  }
+
+  public ComponentPainter getComponentPainter() {
+    return painter;
+  }
+
+  public void setComponentPainter(ComponentPainter painter) {
+    if (this.painter != painter) {
+      this.painter = painter;
+
+      repaint();
+    }
+  }
+
+  public Direction getDirection() {
+    return direction;
+  }
+
+  public boolean isHorizontalFlip() {
+    return horizontalFlip;
+  }
+
+  public void setHorizontalFlip(boolean horizontalFlip) {
+    if (this.horizontalFlip != horizontalFlip) {
+      this.horizontalFlip = horizontalFlip;
+      revalidate();
+    }
+  }
+
+  public boolean isVerticalFlip() {
+    return verticalFlip;
+  }
+
+  public void setVerticalFlip(boolean verticalFlip) {
+    if (this.verticalFlip != verticalFlip) {
+      this.verticalFlip = verticalFlip;
+      revalidate();
+    }
+  }
+
+  public void setDirection(Direction direction) {
+    if (this.direction != direction) {
+      this.direction = direction;
+      revalidate();
+
+      repaint();
+    }
+  }
+
+  public boolean isClipChildren() {
+    return clipChildren;
+  }
+
+  public void setClipChildren(boolean clipChildren) {
+    this.clipChildren = clipChildren;
+  }
+
+  public ShapedBorder getShapedBorder() {
+    return shapedBorder;
+  }
+
+  public void setBorder(Border border) {
+    super.setBorder(border);
+    shapedBorder = null;
+    findShapedBorder(getBorder(), new Insets(0, 0, 0, 0));
+  }
+
+  protected void paintChildren(Graphics g) {
+    if (clipChildren) {
+      Shape shape = getShape();
+
+      if (shape != null) {
+        Graphics2D g2 = (Graphics2D) g;
+        Shape clip = g2.getClip();
+        g2.clip(shape);
+        super.paintChildren(g);
+        g2.setClip(clip);
+
+/*        ShapedBorder sb = getShapedBorder();
+
+        if (sb != null)
+          sb.paintBorder(this, g, shapedInsets.left, shapedInsets.top, getWidth() - shapedInsets.left -shapedInsets.right, getHeight() - shapedInsets.top - shapedInsets.bottom);
+*/
+        return;
+      }
+    }
+
+    super.paintChildren(g);
+  }
+
+  protected void paintComponent(Graphics g) {
+    super.paintComponent(g);
+
+    if (painter != null) {
+      Shape shape = getShape();
+
+      if (shape != null) {
+        Shape clip = g.getClip();
+        g.clipRect(shapedInsets.left,
+                   shapedInsets.top,
+                   getWidth() - shapedInsets.left - shapedInsets.right,
+                   getHeight() - shapedInsets.top - shapedInsets.bottom);
+        ((Graphics2D) g).clip(shape);
+        painter.paint(this, g, 0, 0, getWidth(), getHeight(), direction, horizontalFlip, verticalFlip);
+        g.setClip(clip);
+      }
+      else
+        painter.paint(this, g, 0, 0, getWidth(), getHeight(), direction, horizontalFlip, verticalFlip);
+    }
+  }
+
+  public boolean contains(int x, int y) {
+    if (x < 0 || y < 0 || x >= getWidth() || y >= getHeight())
+      return false;
+
+    Shape shape = getShape();
+    return shape == null ? super.contains(x, y) : shape.contains(x, y);
+  }
+
+  public boolean inside(int x, int y) {
+    if (x < 0 || y < 0 || x >= getWidth() || y >= getHeight())
+      return false;
+
+    Shape shape = getShape();
+    return shape == null ? super.inside(x, y) : shape.contains(x, y);
+  }
+
+  private boolean findShapedBorder(Border border, Insets i) {
+    if (border == null)
+      return false;
+    else if (border instanceof ShapedBorder) {
+      shapedBorder = (ShapedBorder) border;
+      shapedInsets = i;
+      return true;
+    }
+    else if (border instanceof CompoundBorder) {
+      CompoundBorder c = (CompoundBorder) border;
+
+      if (findShapedBorder(c.getOutsideBorder(), i))
+        return true;
+
+      return findShapedBorder(c.getInsideBorder(), InsetsUtil.add(c.getOutsideBorder().getBorderInsets(this), i));
+    }
+    else
+      return false;
+  }
+}
diff --git a/src/net/infonode/properties/base/Property.java b/src/net/infonode/properties/base/Property.java
new file mode 100644
index 0000000..e1d8671
--- /dev/null
+++ b/src/net/infonode/properties/base/Property.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Property.java,v 1.7 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.properties.base;
+
+import net.infonode.properties.base.exception.CantRemoveValueException;
+import net.infonode.properties.base.exception.ImmutablePropertyException;
+import net.infonode.properties.base.exception.InvalidPropertyException;
+import net.infonode.properties.base.exception.InvalidPropertyValueException;
+
+/**
+ * A property is belongs to a {@link PropertyGroup} and contains name, description, type etc.
+ * A property can have multiple values which can be stored in any type of object.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public interface Property {
+  /**
+   * Returns the property name.
+   *
+   * @return the property name
+   */
+  String getName();
+
+  /**
+   * Returns a description of this property.
+   *
+   * @return a description of this property
+   */
+  String getDescription();
+
+  /**
+   * Returns the value type of this property.
+   * The property can only be set to values that are of this class or a sub class of this class.
+   *
+   * @return the value type of this property
+   */
+  Class getType();
+
+  /**
+   * Returns the property group that this property belongs to.
+   *
+   * @return the property group that this property belongs to
+   */
+  PropertyGroup getGroup();
+
+  /**
+   * Returns the value of this property in a value container.
+   *
+   * @param valueContainer the object containing the value
+   * @return the value of this property in an valueContainer, null if the container doesn't contain the value
+   * @throws InvalidPropertyException if the property can not be read from the value container
+   */
+  Object getValue(Object valueContainer) throws InvalidPropertyException;
+
+  /**
+   * Sets the value of this property in an object.
+   *
+   * @param valueContainer the object to set the property value in
+   * @param value          the value of the property
+   * @throws ImmutablePropertyException    if this property is immutable
+   * @throws InvalidPropertyException      if this property can't be set in the object
+   * @throws InvalidPropertyValueException if the property value is invalid
+   */
+  void setValue(Object valueContainer, Object value) throws ImmutablePropertyException, InvalidPropertyException,
+      InvalidPropertyValueException;
+
+  /**
+   * Returns true if the value can be assigned to this property.
+   *
+   * @param value the value to assign
+   * @return true if the value can be assigned to this property
+   */
+  boolean canBeAssiged(Object value);
+
+  /**
+   * Returns true if this property is mutable.
+   *
+   * @return true if this property is mutable
+   */
+  boolean isMutable();
+
+  /**
+   * Returns true if the value of this property can be removed from the valueContainer.
+   *
+   * @param valueContainer the object from which to remove the value
+   * @return true if the value of this property can be removed from the valueContainer
+   */
+  boolean valueIsRemovable(Object valueContainer);
+
+  /**
+   * Returns true if this property has a value in the valueContainer.
+   *
+   * @param valueContainer the object that might contain the value
+   * @return true if this property has a value in the valueContainer
+   */
+  boolean valueIsSet(Object valueContainer);
+
+  /**
+   * Removes the value of this property from an valueContainer.
+   *
+   * @param valueContainer the object in which to remove the value
+   * @throws ImmutablePropertyException if the property is immutable
+   * @throws CantRemoveValueException   if the property value can't be removed from the valueContainer
+   */
+  void removeValue(Object valueContainer) throws ImmutablePropertyException, CantRemoveValueException;
+}
diff --git a/src/net/infonode/properties/base/PropertyGroup.java b/src/net/infonode/properties/base/PropertyGroup.java
new file mode 100644
index 0000000..b4f0eca
--- /dev/null
+++ b/src/net/infonode/properties/base/PropertyGroup.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyGroup.java,v 1.6 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.base;
+
+import java.util.ArrayList;
+
+/**
+ * A group of properties. The group have a name and a description. It can also have a super group from which it inherit
+ * all it's properties. You can think of a property group as similar to a Java class, and properties similar to class
+ * fields.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class PropertyGroup {
+  private PropertyGroup superGroup;
+  private String name;
+  private String description;
+  private ArrayList properties = new ArrayList(10);
+
+  /**
+   * Creates a property group.
+   *
+   * @param name        the name of the group
+   * @param description the group description
+   */
+  public PropertyGroup(String name, String description) {
+    this.name = name;
+    this.description = description;
+  }
+
+  /**
+   * Creates a property group with a super group.
+   * All properties in the super group will be inherited to this group.
+   *
+   * @param superGroup  the super group of this group
+   * @param name        the name of the group
+   * @param description the group description
+   */
+  public PropertyGroup(PropertyGroup superGroup, String name, String description) {
+    this(name, description);
+    this.superGroup = superGroup;
+  }
+
+  /**
+   * Returns the super group of this group.
+   *
+   * @return the super group of this group, null if it has no super group
+   */
+  public PropertyGroup getSuperGroup() {
+    return superGroup;
+  }
+
+  /**
+   * Returns the description for this group.
+   *
+   * @return the description for this group
+   */
+  public String getDescription() {
+    return description;
+  }
+
+  /**
+   * Returns the name of this group.
+   *
+   * @return the name of this group
+   */
+  public String getName() {
+    return name;
+  }
+
+  /**
+   * Add a property to this group.
+   *
+   * @param property the property to add
+   */
+  public void addProperty(Property property) {
+    properties.add(property);
+  }
+
+  /**
+   * Returns the number of properties in this group.
+   * This does not include properties in super groups.
+   *
+   * @return the number of properties in this group
+   */
+  public int getPropertyCount() {
+    return properties.size();
+  }
+
+  /**
+   * Returns true if this group or one of it's super groups contains the property.
+   *
+   * @param property the property
+   * @return true if this group or one of it's super groups contains the property
+   */
+  public boolean hasProperty(Property property) {
+    return isA(property.getGroup());
+  }
+
+  /**
+   * Returns the property at the index,
+   * This does not include properties in super groups.
+   *
+   * @param index the property index
+   * @return the property at the index
+   */
+  public Property getProperty(int index) {
+    return (Property) properties.get(index);
+  }
+
+  /**
+   * Returns an array with the properties in this group.
+   * This does not include properties in super groups.
+   *
+   * @return an array with the properties in this group
+   */
+  public Property[] getProperties() {
+    return (Property[]) properties.toArray(new Property[properties.size()]);
+  }
+
+  public String toString() {
+    return getName();
+  }
+
+  /**
+   * Returns the property with the given name.
+   * This includes properties in super groups.
+   *
+   * @param name the property name
+   * @return the property with the given name, null if no property was found
+   */
+  public Property getProperty(String name) {
+    for (int i = 0; i < getPropertyCount(); i++) {
+      if (getProperty(i).getName().equals(name))
+        return getProperty(i);
+    }
+
+    return superGroup == null ? null : superGroup.getProperty(name);
+  }
+
+  /**
+   * Returns true if the group is this group or one of it's super groups.
+   *
+   * @param group the group
+   * @return true if the group is this group or one of it's super groups
+   */
+  private boolean isA(PropertyGroup group) {
+    return group == this || (getSuperGroup() != null && getSuperGroup().isA(group));
+  }
+
+}
diff --git a/src/net/infonode/properties/base/exception/CantRemoveValueException.java b/src/net/infonode/properties/base/exception/CantRemoveValueException.java
new file mode 100644
index 0000000..4e23cb7
--- /dev/null
+++ b/src/net/infonode/properties/base/exception/CantRemoveValueException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: CantRemoveValueException.java,v 1.2 2004/06/17 15:07:10 johan Exp $
+package net.infonode.properties.base.exception;
+
+import net.infonode.properties.base.Property;
+
+/**
+ * Exception thrown when a property value can't be removed.
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.2 $
+ */
+public class CantRemoveValueException extends PropertyException {
+  /**
+   * Constructor.
+   *
+   * @param property the property which triggered this exception
+   */
+  public CantRemoveValueException(Property property) {
+    super(property, "Can't remove property value for '" + property.getName() + "'!");
+  }
+
+}
diff --git a/src/net/infonode/properties/base/exception/ImmutablePropertyException.java b/src/net/infonode/properties/base/exception/ImmutablePropertyException.java
new file mode 100644
index 0000000..7c4c068
--- /dev/null
+++ b/src/net/infonode/properties/base/exception/ImmutablePropertyException.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ImmutablePropertyException.java,v 1.2 2004/06/17 15:07:10 johan Exp $
+package net.infonode.properties.base.exception;
+
+import net.infonode.properties.base.Property;
+
+/**
+ * Exception thrown when trying to modify an immutable property.
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.2 $
+ */
+public class ImmutablePropertyException extends PropertyException {
+  /**
+   * Constructor.
+   *
+   * @param property the property that triggered this exception
+   */
+  public ImmutablePropertyException(Property property) {
+    super(property, "Can't modify immutable property '" + property.getName() + "'!");
+  }
+
+}
diff --git a/src/net/infonode/properties/base/exception/InvalidPropertyException.java b/src/net/infonode/properties/base/exception/InvalidPropertyException.java
new file mode 100644
index 0000000..e6b7d33
--- /dev/null
+++ b/src/net/infonode/properties/base/exception/InvalidPropertyException.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InvalidPropertyException.java,v 1.3 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.base.exception;
+
+import net.infonode.properties.base.Property;
+
+/**
+ * Exception thrown when an invalid property was given.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class InvalidPropertyException extends PropertyException {
+  /**
+   * Constructor.
+   *
+   * @param property the property that triggered this exception
+   * @param text     the exception text
+   */
+  public InvalidPropertyException(Property property, String text) {
+    super(property, text);
+  }
+
+}
diff --git a/src/net/infonode/properties/base/exception/InvalidPropertyTypeException.java b/src/net/infonode/properties/base/exception/InvalidPropertyTypeException.java
new file mode 100644
index 0000000..5134d54
--- /dev/null
+++ b/src/net/infonode/properties/base/exception/InvalidPropertyTypeException.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InvalidPropertyTypeException.java,v 1.4 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.base.exception;
+
+import net.infonode.properties.base.Property;
+
+/**
+ * Thrown when a property type is incompatible with another property type.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class InvalidPropertyTypeException extends PropertyException {
+  private Property invalidProperty;
+
+  /**
+   * Constructor.
+   *
+   * @param property        the property
+   * @param invalidProperty the property which type is incompatible with the property type
+   * @param text            the exception text
+   */
+  public InvalidPropertyTypeException(Property property, Property invalidProperty, String text) {
+    super(property, text);
+    this.invalidProperty = invalidProperty;
+  }
+
+  /**
+   * Returns the property which type is incompatible with the property type.
+   *
+   * @return the property which type is incompatible with the property type
+   */
+  public Property getInvalidProperty() {
+    return invalidProperty;
+  }
+
+}
diff --git a/src/net/infonode/properties/base/exception/InvalidPropertyValueException.java b/src/net/infonode/properties/base/exception/InvalidPropertyValueException.java
new file mode 100644
index 0000000..4eb1c5e
--- /dev/null
+++ b/src/net/infonode/properties/base/exception/InvalidPropertyValueException.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InvalidPropertyValueException.java,v 1.3 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.base.exception;
+
+import net.infonode.properties.base.Property;
+
+/**
+ * An invalid property value was given.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class InvalidPropertyValueException extends PropertyException {
+  private Object value;
+
+  /**
+   * Constructor.
+   *
+   * @param property the property that was assigned a value
+   * @param value    the value
+   */
+  public InvalidPropertyValueException(Property property, Object value) {
+    super(property, "Property '" + property + "' can't be assigned the value '" + value + "'!");
+    this.value = value;
+  }
+
+  /**
+   * Returns the value.
+   *
+   * @return the value
+   */
+  public Object getValue() {
+    return value;
+  }
+}
diff --git a/src/net/infonode/properties/base/exception/PropertyException.java b/src/net/infonode/properties/base/exception/PropertyException.java
new file mode 100644
index 0000000..72b83a8
--- /dev/null
+++ b/src/net/infonode/properties/base/exception/PropertyException.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyException.java,v 1.3 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.base.exception;
+
+import net.infonode.properties.base.Property;
+
+/**
+ * Base class for all property exceptions.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class PropertyException extends RuntimeException {
+  private Property property;
+
+  /**
+   * Constructor.
+   *
+   * @param property the property that triggered this exception
+   * @param text     the exception text
+   */
+  public PropertyException(Property property, String text) {
+    super(text);
+    this.property = property;
+  }
+
+  /**
+   * Returns the property that triggered this exception.
+   *
+   * @return the property that triggered this exception
+   */
+  public Property getProperty() {
+    return property;
+  }
+}
diff --git a/src/net/infonode/properties/base/exception/package.html b/src/net/infonode/properties/base/exception/package.html
new file mode 100644
index 0000000..8f390da
--- /dev/null
+++ b/src/net/infonode/properties/base/exception/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Properties exception classes.
+</body>
+</html>
diff --git a/src/net/infonode/properties/base/package.html b/src/net/infonode/properties/base/package.html
new file mode 100644
index 0000000..288b42e
--- /dev/null
+++ b/src/net/infonode/properties/base/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Base classes for the properties library.
+</body>
+</html>
diff --git a/src/net/infonode/properties/gui/InternalPropertiesUtil.java b/src/net/infonode/properties/gui/InternalPropertiesUtil.java
new file mode 100644
index 0000000..5c9d22b
--- /dev/null
+++ b/src/net/infonode/properties/gui/InternalPropertiesUtil.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InternalPropertiesUtil.java,v 1.5 2005/12/04 13:46:06 jesper Exp $
+package net.infonode.properties.gui;
+
+import net.infonode.gui.panel.BaseContainerUtil;
+import net.infonode.gui.shaped.panel.ShapedPanel;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.util.Direction;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class InternalPropertiesUtil {
+  private InternalPropertiesUtil() {
+  }
+
+  /**
+   * Applies the property values to a shaped panel.
+   *
+   * @param panel the shaped panel on which to apply the property values
+   */
+  public static final void applyTo(ShapedPanelProperties properties, ShapedPanel panel) {
+    applyTo(properties, panel, null);
+  }
+
+  /**
+   * Applies the property values to a shaped panel.
+   *
+   * @param panel the shaped panel on which to apply the property values
+   */
+  public static final void applyTo(ShapedPanelProperties properties, ShapedPanel panel, Direction d) {
+    panel.setHorizontalFlip(properties.getHorizontalFlip());
+    panel.setVerticalFlip(properties.getVerticalFlip());
+    panel.setComponentPainter(properties.getComponentPainter());
+    panel.setDirection(d == null ? properties.getDirection() : d);
+    panel.setClipChildren(properties.getClipChildren());
+    BaseContainerUtil.setForcedOpaque(panel, properties.getOpaque());
+    //panel.setForcedOpaque(properties.getOpaque());
+  }
+}
diff --git a/src/net/infonode/properties/gui/util/ButtonProperties.java b/src/net/infonode/properties/gui/util/ButtonProperties.java
new file mode 100644
index 0000000..bb22eea
--- /dev/null
+++ b/src/net/infonode/properties/gui/util/ButtonProperties.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ButtonProperties.java,v 1.3 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.gui.util;
+
+import net.infonode.gui.button.ButtonFactory;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.ButtonFactoryProperty;
+import net.infonode.properties.types.IconProperty;
+import net.infonode.properties.types.StringProperty;
+
+import javax.swing.*;
+
+/**
+ * Properties and property values for a button.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class ButtonProperties extends PropertyMapContainer {
+  /**
+   * Property group for all button properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Button Properties", "");
+
+  /**
+   * The button icon.
+   */
+  public static final IconProperty ICON = new IconProperty(PROPERTIES, "Icon", "Icon for the enabled button state.",
+                                                           PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The disabled button icon.
+   */
+  public static final IconProperty DISABLED_ICON = new IconProperty(PROPERTIES, "Disabled Icon",
+                                                                    "Icon for the disabled button state.",
+                                                                    PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The enabled button tool tip text.
+   */
+  public static final StringProperty TOOL_TIP_TEXT = new StringProperty(PROPERTIES,
+                                                                        "Tool Tip Text",
+                                                                        "The button tool tip text.",
+                                                                        PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * <p>The button factory.</p>
+   *
+   * <p>The created button will be assigned the icon from
+   * {@link #ICON} or {@link #DISABLED_ICON} and the tool tip from {@link #TOOL_TIP_TEXT}. An action listener is also added to the button.
+   * </p>
+   */
+  public static final ButtonFactoryProperty FACTORY = new ButtonFactoryProperty(PROPERTIES,
+                                                                                "Factory",
+                                                                                "The button factory. This factory is used to create a button. The " +
+                                                                                "created button will be assigned the icon from the '" +
+                                                                                ICON.getName() + "' property or the '" +
+                                                                                DISABLED_ICON.getName() +
+                                                                                "' property and the tool tip from the '" +
+                                                                                TOOL_TIP_TEXT.getName() +
+                                                                                "' " +
+                                                                                "property. An action listener is also added to the button.",
+                                                                                PropertyMapValueHandler.INSTANCE);
+
+  static {
+    ButtonProperties properties = new ButtonProperties(PROPERTIES.getDefaultMap());
+    properties.setIcon(null).setDisabledIcon(null).setToolTipText(null);
+  }
+
+  /**
+   * Creates an empty property object.
+   */
+  public ButtonProperties() {
+    super(PROPERTIES);
+  }
+
+  /**
+   * Creates a property map containing the map.
+   *
+   * @param map the property map
+   */
+  public ButtonProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public ButtonProperties(ButtonProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public ButtonProperties addSuperObject(ButtonProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   */
+  public ButtonProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes the given super object.
+   *
+   * @param superObject super object to remove
+   * @return this
+   */
+  public ButtonProperties removeSuperObject(ButtonProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Sets the button icon.
+   *
+   * @param icon the button icon
+   * @return this
+   */
+  public ButtonProperties setIcon(Icon icon) {
+    ICON.set(getMap(), icon);
+    return this;
+  }
+
+  /**
+   * Returns the button icon.
+   *
+   * @return the button icon
+   */
+  public Icon getIcon() {
+    return ICON.get(getMap());
+  }
+
+  /**
+   * Sets the disabled button icon.
+   *
+   * @param icon the disabled button icon
+   * @return this
+   */
+  public ButtonProperties setDisabledIcon(Icon icon) {
+    DISABLED_ICON.set(getMap(), icon);
+    return this;
+  }
+
+  /**
+   * Returns the disabled button icon.
+   *
+   * @return the disabled button icon
+   */
+  public Icon getDisabledIcon() {
+    return DISABLED_ICON.get(getMap());
+  }
+
+  /**
+   * Returns the button tool tip text.
+   *
+   * @return the button tool tip text
+   */
+  public String getToolTipText() {
+    return TOOL_TIP_TEXT.get(getMap());
+  }
+
+  /**
+   * Sets the button tool tip text.
+   *
+   * @param text the button tool tip text
+   * @return this
+   */
+  public ButtonProperties setToolTipText(String text) {
+    TOOL_TIP_TEXT.set(getMap(), text);
+    return this;
+  }
+
+  /**
+   * <p>Gets the button factory.</p>
+   *
+   * <p>The created button will be assigned the icon from
+   * {@link #ICON} or {@link #DISABLED_ICON} and the tool tip from {@link #TOOL_TIP_TEXT}.
+   * An action listener is also added to the button.
+   * </p>
+   *
+   * @return the button factory
+   */
+  public ButtonFactory getFactory() {
+    return FACTORY.get(getMap());
+  }
+
+  /**
+   * <p>Sets the button factory.</p>
+   *
+   * <p>The created button will be assigned the icon from
+   * {@link #ICON} or {@link #DISABLED_ICON} and the tool tip from {@link #TOOL_TIP_TEXT}.
+   * An action listener is also added to the button.
+   * </p>
+   *
+   * @param factory the button factory
+   * @return this
+   */
+  public ButtonProperties setFactory(ButtonFactory factory) {
+    FACTORY.set(getMap(), factory);
+    return this;
+  }
+
+  /**
+   * Applies the icon, disabled icon and tool tip to the given button
+   *
+   * @param button botton
+   * @return the button
+   */
+  public AbstractButton applyTo(AbstractButton button) {
+    button.setIcon(getIcon());
+    button.setDisabledIcon(getDisabledIcon());
+    button.setToolTipText(getToolTipText());
+
+    return button;
+  }
+}
diff --git a/src/net/infonode/properties/gui/util/ComponentProperties.java b/src/net/infonode/properties/gui/util/ComponentProperties.java
new file mode 100644
index 0000000..f5e5a84
--- /dev/null
+++ b/src/net/infonode/properties/gui/util/ComponentProperties.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ComponentProperties.java,v 1.17 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.properties.gui.util;
+
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.panel.BaseContainer;
+import net.infonode.gui.panel.BaseContainerUtil;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BorderProperty;
+import net.infonode.properties.types.ColorProperty;
+import net.infonode.properties.types.FontProperty;
+import net.infonode.properties.types.InsetsProperty;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+
+/**
+ * Properties and property values for a {@link JComponent}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.17 $
+ */
+public class ComponentProperties extends PropertyMapContainer {
+  /**
+   * Property group for all component properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Component Properties", "");
+
+  /**
+   * Component border.
+   */
+  public static final BorderProperty BORDER = new BorderProperty(PROPERTIES, "Border", "Component border.",
+                                                                 PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Component insets inside the border.
+   */
+  public static final InsetsProperty INSETS = new InsetsProperty(PROPERTIES, "Insets",
+                                                                 "Component insets inside the border.",
+                                                                 PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Component foreground color.
+   */
+  public static final ColorProperty FOREGROUND_COLOR = new ColorProperty(PROPERTIES, "Foreground Color",
+                                                                         "Component foreground color.",
+                                                                         PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Component text font.
+   */
+  public static final FontProperty FONT = new FontProperty(PROPERTIES, "Font", "Component text font.",
+                                                           PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Component background color. A null value means that no background will be painted.
+   */
+  public static final ColorProperty BACKGROUND_COLOR = new ColorProperty(PROPERTIES, "Background Color",
+                                                                         "Component background color. A null value means that no background will be painted.",
+                                                                         PropertyMapValueHandler.INSTANCE);
+
+  static {
+    ComponentProperties properties = new ComponentProperties(PROPERTIES.getDefaultMap());
+
+    properties.setBackgroundColor(null).setBorder(null).setInsets(null);
+  }
+
+  /**
+   * Creates an empty property object.
+   */
+  public ComponentProperties() {
+    super(PROPERTIES);
+  }
+
+  /**
+   * Creates a property map containing the map.
+   *
+   * @param map the property map
+   */
+  public ComponentProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public ComponentProperties(ComponentProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public ComponentProperties addSuperObject(ComponentProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   */
+  public ComponentProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes the given super object.
+   *
+   * @param superObject super object to remove
+   * @return this
+   */
+  public ComponentProperties removeSuperObject(ComponentProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Sets the component border.
+   *
+   * @param border the component border
+   * @return this
+   */
+  public ComponentProperties setBorder(Border border) {
+    BORDER.set(getMap(), border);
+
+    return this;
+  }
+
+  /**
+   * Sets the component insets inside the border.
+   *
+   * @param insets the component insets
+   * @return this
+   */
+  public ComponentProperties setInsets(Insets insets) {
+    INSETS.set(getMap(), insets);
+
+    return this;
+  }
+
+  /**
+   * Sets the component background color.
+   *
+   * @param color the background color, null means no background
+   * @return this
+   */
+  public ComponentProperties setBackgroundColor(Color color) {
+    BACKGROUND_COLOR.set(getMap(), color);
+
+    return this;
+  }
+
+  /**
+   * Returns the component insets inside the border.
+   *
+   * @return the component insets inside the border
+   */
+  public Insets getInsets() {
+    return INSETS.get(getMap());
+  }
+
+  /**
+   * Returns the component border.
+   *
+   * @return the component border
+   */
+  public Border getBorder() {
+    return BORDER.get(getMap());
+  }
+
+  /**
+   * Returns the component background color.
+   *
+   * @return the component background color
+   */
+  public Color getBackgroundColor() {
+    return BACKGROUND_COLOR.get(getMap());
+  }
+
+  /**
+   * Returns the component text font.
+   *
+   * @return the component text font
+   */
+  public Font getFont() {
+    return FONT.get(getMap());
+  }
+
+  /**
+   * Returns the component foreground color.
+   *
+   * @return the component foreground color
+   */
+  public Color getForegroundColor() {
+    return FOREGROUND_COLOR.get(getMap());
+  }
+
+  /**
+   * Sets the component foreground color.
+   *
+   * @param foregroundColor the component foreground color
+   * @return this
+   */
+  public ComponentProperties setForegroundColor(Color foregroundColor) {
+    FOREGROUND_COLOR.set(getMap(), foregroundColor);
+    return this;
+  }
+
+  /**
+   * Sets the component text font.
+   *
+   * @param font the component text font
+   * @return this
+   */
+  public ComponentProperties setFont(Font font) {
+    FONT.set(getMap(), font);
+    return this;
+  }
+
+  /**
+   * Applies the property values to a component.
+   *
+   * @param component the component on which to apply the property values
+   */
+  public void applyTo(JComponent component) {
+    applyTo(component, Direction.RIGHT);
+  }
+
+  /**
+   * Applies the property values to a component and rotates the insets in the
+   * given direction.
+   *
+   * @param component       the component on which to apply the property values
+   * @param insetsDirection insets direction
+   */
+  public void applyTo(JComponent component, Direction insetsDirection) {
+    Insets insets = getInsets() == null ? null : InsetsUtil.rotate(insetsDirection, getInsets());
+    Border innerBorder = (insets == null) ? null : new EmptyBorder(insets);
+    component.setBorder((getBorder() == null) ? innerBorder
+                        : ((innerBorder == null) ? getBorder()
+                           : new CompoundBorder(getBorder(), innerBorder)));
+    //component.setOpaque(getBackgroundColor() != null);
+    
+    if (component instanceof BaseContainer) {
+      BaseContainer c = (BaseContainer) component;
+      BaseContainerUtil.setOverridedBackground(c, getBackgroundColor());
+      BaseContainerUtil.setOverridedForeground(c, getForegroundColor());
+      BaseContainerUtil.setOverridedFont(c, getFont());
+    }
+    else {
+      component.setBackground(getBackgroundColor());
+      component.setFont(getFont());
+      component.setForeground(getForegroundColor());
+    }
+  }
+}
diff --git a/src/net/infonode/properties/gui/util/ShapedPanelProperties.java b/src/net/infonode/properties/gui/util/ShapedPanelProperties.java
new file mode 100644
index 0000000..2c30606
--- /dev/null
+++ b/src/net/infonode/properties/gui/util/ShapedPanelProperties.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ShapedPanelProperties.java,v 1.11 2005/12/04 13:46:05 jesper Exp $
+
+package net.infonode.properties.gui.util;
+
+import net.infonode.gui.componentpainter.ComponentPainter;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+import net.infonode.properties.types.ComponentPainterProperty;
+import net.infonode.properties.types.DirectionProperty;
+import net.infonode.util.Direction;
+
+/**
+ * Properties and property values for a shaped panel, which is a panel that can have a
+ * {@link net.infonode.gui.shaped.border.ShapedBorder} and a {@link ComponentPainter}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.11 $
+ */
+public class ShapedPanelProperties extends PropertyMapContainer {
+  /**
+   * Property group for all shaped panel properties.
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Shaped Panel Properties", "");
+
+
+  /**
+   * If true the shaped panel is opaque.
+   *
+   * @since ITP 1.4.0
+   */
+  public static final BooleanProperty OPAQUE =
+      new BooleanProperty(PROPERTIES,
+                          "Opaque",
+                          "If true the shaped panel is opaque. If false the shaped panel is transparent",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * If true the shaped panel is flipped horizontally.
+   * Used by {@link ComponentPainter}'s, {@link net.infonode.gui.shaped.border.ShapedBorder}'s etc.
+   */
+  public static final BooleanProperty HORIZONTAL_FLIP =
+      new BooleanProperty(PROPERTIES,
+                          "Horizontal Flip",
+                          "If true the shaped panel is flipped horizontally. " +
+                          "Used by ComponentPainter's, ShapedBorder's etc.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * If true the shaped panel is flipped vertically.
+   * Used by {@link ComponentPainter}'s, {@link net.infonode.gui.shaped.border.ShapedBorder}'s etc.
+   */
+  public static final BooleanProperty VERTICAL_FLIP =
+      new BooleanProperty(PROPERTIES,
+                          "Vertical Flip",
+                          "If true the shaped panel is flipped vertically. " +
+                          "Used by ComponentPainter's, ShapedBorder's etc.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * If true the child components of the shaped panel are clipped with the border shape.
+   */
+  public static final BooleanProperty CLIP_CHILDREN =
+      new BooleanProperty(PROPERTIES,
+                          "Clip Children",
+                          "If true the child components of the shaped panel are clipped with the border shape.",
+                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * {@link ComponentPainter} that paints the shaped panel background.
+   */
+  public static final ComponentPainterProperty COMPONENT_PAINTER =
+      new ComponentPainterProperty(PROPERTIES,
+                                   "Component Painter",
+                                   "The component painter that paints the shaped panel background.",
+                                   PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * The direction of the shaped panel.
+   * Used by {@link ComponentPainter}'s, {@link net.infonode.gui.shaped.border.ShapedBorder}'s etc.
+   */
+  public static final DirectionProperty DIRECTION =
+      new DirectionProperty(PROPERTIES,
+                            "Direction",
+                            "The direction of the shaped panel. Used by ComponentPainter's, ShapedBorder's etc.",
+                            PropertyMapValueHandler.INSTANCE);
+
+  static {
+    ShapedPanelProperties properties = new ShapedPanelProperties(PROPERTIES.getDefaultMap());
+
+    properties.setHorizontalFlip(false).setVerticalFlip(false).setComponentPainter(null).setDirection(Direction.RIGHT);
+  }
+
+  /**
+   * Creates an empty property object.
+   */
+  public ShapedPanelProperties() {
+    super(PROPERTIES);
+  }
+
+  /**
+   * Creates a property map containing the map.
+   *
+   * @param map the property map
+   */
+  public ShapedPanelProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Creates a property object that inherit values from another property
+   * object.
+   *
+   * @param inheritFrom the object from which to inherit property values
+   */
+  public ShapedPanelProperties(ShapedPanelProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param properties the object from which to inherit property values
+   * @return this
+   */
+  public ShapedPanelProperties addSuperObject(ShapedPanelProperties properties) {
+    getMap().addSuperMap(properties.getMap());
+
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   */
+  public ShapedPanelProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes the given super object.
+   *
+   * @param superObject super object to remove
+   * @return this
+   */
+  public ShapedPanelProperties removeSuperObject(ShapedPanelProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Set to true if the shaped panel should be opaque.
+   *
+   * @param opaque true for opaque, otherwise false
+   * @since ITP 1.4.0
+   */
+  public ShapedPanelProperties setOpaque(boolean opaque) {
+    OPAQUE.set(getMap(), opaque);
+
+    return this;
+  }
+
+  /**
+   * Returns true if the shaped panel should be opaque.
+   *
+   * @return true for opaque, otherwise false
+   * @since ITP 1.4.0
+   */
+  public boolean getOpaque() {
+    return OPAQUE.get(getMap());
+  }
+
+  /**
+   * Set to true if the shaped panel should be flipped horizontally.
+   * Used by {@link ComponentPainter}'s, {@link net.infonode.gui.shaped.border.ShapedBorder}'s etc.
+   *
+   * @param flip true if the shaped panel should be flipped vertically
+   * @return this
+   */
+  public ShapedPanelProperties setHorizontalFlip(boolean flip) {
+    HORIZONTAL_FLIP.set(getMap(), flip);
+
+    return this;
+  }
+
+  /**
+   * Returns true if the shaped panel is flipped horizontally.
+   * Used by {@link ComponentPainter}'s, {@link net.infonode.gui.shaped.border.ShapedBorder}'s etc.
+   *
+   * @return true if the shaped panel is flipped horizontally
+   */
+  public boolean getHorizontalFlip() {
+    return HORIZONTAL_FLIP.get(getMap());
+  }
+
+  /**
+   * Set to true if the shaped panel should be flipped vertically.
+   * Used by {@link ComponentPainter}'s, {@link net.infonode.gui.shaped.border.ShapedBorder}'s etc.
+   *
+   * @param flip true if the shaped panel should be flipped horizontally
+   * @return this
+   */
+  public ShapedPanelProperties setVerticalFlip(boolean flip) {
+    VERTICAL_FLIP.set(getMap(), flip);
+
+    return this;
+  }
+
+  /**
+   * Returns true if the shaped panel is flipped vertically.
+   * Used by {@link ComponentPainter}'s, {@link net.infonode.gui.shaped.border.ShapedBorder}'s etc.
+   *
+   * @return true if the shaped panel is flipped vertically
+   */
+  public boolean getVerticalFlip() {
+    return VERTICAL_FLIP.get(getMap());
+  }
+
+  /**
+   * Set to true if the child components of the shaped panel should be clipped with the border shape.
+   *
+   * @param clipChildren true if the child components of the shaped panel should be clipped with the border shape
+   * @return this
+   */
+  public ShapedPanelProperties setClipChildren(boolean clipChildren) {
+    CLIP_CHILDREN.set(getMap(), clipChildren);
+    return this;
+  }
+
+  /**
+   * Returns true the child components of the shaped panel are clipped with the border shape.
+   *
+   * @return true the child components of the shaped panel are clipped with the border shape
+   */
+  public boolean getClipChildren() {
+    return CLIP_CHILDREN.get(getMap());
+  }
+
+  /**
+   * Sets the painter that paints the shaped panel background.
+   *
+   * @param painter the painter that paints the shaped panel background, null for none
+   * @return this
+   */
+  public ShapedPanelProperties setComponentPainter(ComponentPainter painter) {
+    COMPONENT_PAINTER.set(getMap(), painter);
+
+    return this;
+  }
+
+  /**
+   * Gets the painter that paints the shaped panel background.
+   *
+   * @return the painter that paints the shaped panel background, null if none
+   */
+  public ComponentPainter getComponentPainter() {
+    return COMPONENT_PAINTER.get(getMap());
+  }
+
+  /**
+   * Sets the direction of the shaped panel.
+   * Used by {@link ComponentPainter}'s, {@link net.infonode.gui.shaped.border.ShapedBorder}'s etc.
+   *
+   * @param direction the direction of the shaped panel
+   * @return this
+   */
+  public ShapedPanelProperties setDirection(Direction direction) {
+    DIRECTION.set(getMap(), direction);
+
+    return this;
+  }
+
+  /**
+   * Gets the direction of the shaped panel.
+   * Used by {@link ComponentPainter}'s, {@link net.infonode.gui.shaped.border.ShapedBorder}'s etc.
+   *
+   * @return the direction of the shaped panel
+   */
+  public Direction getDirection() {
+    return DIRECTION.get(getMap());
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/properties/gui/util/package.html b/src/net/infonode/properties/gui/util/package.html
new file mode 100644
index 0000000..3ea32ce
--- /dev/null
+++ b/src/net/infonode/properties/gui/util/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Utility GUI classes for properties.
+</body>
+</html>
diff --git a/src/net/infonode/properties/propertymap/PropertyMap.java b/src/net/infonode/properties/propertymap/PropertyMap.java
new file mode 100644
index 0000000..b740e01
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMap.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMap.java,v 1.20 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.propertymap;
+
+import net.infonode.properties.base.Property;
+import net.infonode.properties.base.exception.InvalidPropertyException;
+import net.infonode.properties.base.exception.InvalidPropertyTypeException;
+import net.infonode.properties.util.PropertyChangeListener;
+import net.infonode.util.ReadWritable;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * A property map contains values for some or all properties in a {@link PropertyMapGroup}. A property map
+ * can have any number of super maps from which property values are inherited. Super maps that are searched for
+ * values in the reverse order they were added to the property map. Property values are always set in the property
+ * map specified.
+ * <p>
+ * Properties of type {@link PropertyMapProperty} in the {@link PropertyMapGroup} will automatically be assigned
+ * new PropertyMap's as values. These PropertyMap's are called child maps. These property values cannot be
+ * modified.
+ * <p>
+ * Listeners can be added to a PropertyMap. The listeners are notified when a property value is modified in the
+ * PropertyMap or, if the property value is not overridden, one of it's super maps. A tree listener can also
+ * be added that listens for value changes in the property map, it's super maps and it's child mapss.
+ * <p>
+ * Property maps are created using the factory methods in {@link PropertyMapFactory}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.20 $
+ */
+public interface PropertyMap extends ReadWritable {
+  /**
+   * Adds a listener that listens for value changes in this PropertyMap.
+   * This listener will be notified of updates to values in this PropertyMap and super maps unless the property
+   * value is overridden.
+   *
+   * @param listener the listener
+   */
+  void addListener(PropertyMapListener listener);
+
+  /**
+   * Removes a listener which was previously added with {@link #addListener(PropertyMapListener)}.
+   *
+   * @param listener the listener
+   */
+  void removeListener(PropertyMapListener listener);
+
+  /**
+   * Adds a tree listener that listens for value changes in this PropertyMap or any child maps.
+   * This listener will be notified of updates to values in this PropertyMap, any child map recusively and super
+   * maps unless the property value is overridden.
+   *
+   * @param listener the listener
+   */
+  void addTreeListener(PropertyMapTreeListener listener);
+
+  /**
+   * Removes a previously added tree listener.
+   *
+   * @param listener the listener
+   */
+  void removeTreeListener(PropertyMapTreeListener listener);
+
+  /**
+   * Adds a property listener that listens for value changes for a specific property.
+   * This listener will be notified of value changes for the property in this PropertyMap and super maps unless
+   * the property value is overridden.
+   *
+   * @param property the property to listen to changes on
+   * @param listener the listener
+   */
+  void addPropertyChangeListener(Property property, PropertyChangeListener listener);
+
+  /**
+   * Removes a previously added property listener.
+   *
+   * @param property the property which the listener listens to changes on
+   * @param listener the listener
+   */
+  void removePropertyChangeListener(Property property, PropertyChangeListener listener);
+
+  /**
+   * Adds a super map to this map.
+   * If a property value is not found in this property map, the super maps will be searched recursively. The
+   * super map last added will be searched first.
+   *
+   * @param superMap the super map
+   */
+  void addSuperMap(PropertyMap superMap);
+
+  /**
+   * Removes the most recently added super map.
+   *
+   * @return the super map removed
+   */
+  PropertyMap removeSuperMap();
+
+  /**
+   * Removes a super map that has previously been added using {@link #addSuperMap(PropertyMap)}.
+   *
+   * @param superMap the super map to remove
+   * @return true if the super map was found and removed, otherwise false
+   * @since IDW 1.3.0
+   */
+  boolean removeSuperMap(PropertyMap superMap);
+
+  /**
+   * Replaces a super map that has previously been added using {@link #addSuperMap(PropertyMap)}.
+   *
+   * @param oldSuperMap the super map to replace
+   * @param newSuperMap the super map to replace it with
+   * @return true if the super map was found and replaced, otherwise false
+   * @since IDW 1.3.0
+   */
+  boolean replaceSuperMap(PropertyMap oldSuperMap, PropertyMap newSuperMap);
+
+  /**
+   * Returns the most recently added super map.
+   *
+   * @return the super map
+   */
+  PropertyMap getSuperMap();
+
+  /**
+   * Creates a relative reference from one property value to another property value.
+   * <p>
+   * When the value of the <tt>fromProperty</tt> is read, it will return the value of the <tt>toProperty</tt> in the
+   * <tt>toMap</tt>.
+   * <p>
+   * Sub maps of this property map will inherit this reference relative to themselves, ie the reference in the sub
+   * map is converted to a reference relative to the sub map if possible, otherwise the reference is the same as
+   * for the super map. Here is an example:
+   * <p>
+   * <ul>
+   * <li>Property map A contains value 5 for property X.</li>
+   * <li>A relative reference is created in map A from property Y to property X. Getting the property value for Y in
+   * A will now return 5.</li>
+   * <li>A property map B is created and A is added as super map to B. Note that now B.Y will reference B.X and
+   * not A.X! Getting B.X now returns 5 and B.Y also returns 5.</li>
+   * <li>X is set to 7 in B. Getting B.Y will now return 7 as expected. Map A is unchanged and will still return
+   * 5 as value for property Y.</li>
+   * <li>A.Y is set to 1 which destroys the reference to A.X, and also the reference B.Y -> B.X. Getting B.Y will now
+   * return 1 as it's inherited from A.Y.
+   * </ul>
+   * <p>
+   * Changes to the referenced property value will be propagated to listeners of this property.
+   *
+   * @param fromProperty the property value that will hold the reference
+   * @param toMap        the property map that holds the property value that is referenced
+   * @param toProperty   the property which value is referenced
+   * @return the old value that the fromProperty had in this property map
+   * @throws InvalidPropertyTypeException
+   */
+  Object createRelativeRef(Property fromProperty, PropertyMap toMap, Property toProperty) throws InvalidPropertyTypeException;
+
+  /**
+   * Removes a property value.
+   *
+   * @param property the property
+   * @return the value removed
+   * @throws InvalidPropertyException if values for this property can't be stored in this property map
+   */
+  Object removeValue(Property property) throws InvalidPropertyException;
+
+  /**
+   * Returns true if this property map doesn't contain any property values.
+   *
+   * @param recursive true if child maps should be recursively checked
+   * @return true if this property map doesn't contain any property values
+   */
+  boolean isEmpty(boolean recursive);
+
+  /**
+   * Removes all property values in this property map.
+   *
+   * @param recursive true if child maps should be cleared recursively
+   */
+  void clear(boolean recursive);
+
+  /**
+   * Returns true if all the values in this property map is equal to the values in the given map.
+   * The property values are compared using {@link Object#equals}.
+   *
+   * @param propertyMap the map to compare values with
+   * @param recursive   true if child maps should be recursively checked
+   * @return true if all the values in this property map is equal to the values in the given map
+   */
+  boolean valuesEqualTo(PropertyMap propertyMap, boolean recursive);
+
+  /**
+   * Serializes the serializable values of this property map. Values not implementing the {@link java.io.Serializable}
+   * interface will not be written to the stream. The properties are identified using their names.
+   *
+   * @param out       the stream on which to serialize this map
+   * @param recursive true if child maps should be recursively serialized
+   * @throws IOException if there is an error in the stream
+   */
+  void write(ObjectOutputStream out, boolean recursive) throws IOException;
+
+  /**
+   * <p>
+   * Serializes the serializable values of this property map. Values not implementing the {@link java.io.Serializable}
+   * interface will not be written to the stream. The properties are identified using their names.
+   * </p>
+   * <p>
+   * This method recursively writes all child maps.
+   * </p>
+   *
+   * @param out the stream
+   * @throws IOException if there is a stream error
+   */
+  void write(ObjectOutputStream out) throws IOException;
+
+  /**
+   * Reads property values from a stream and sets them in this map.
+   * Will overwrite existing values, but not remove values not found in the stream.
+   * The properties are identified using their names.
+   * If no property is found for a property name read from the stream the value is skipped and no error is reported.
+   * If a value for a property in the stream is a reference to a another property value that cannot be resolved,
+   * the property is not modified.
+   *
+   * @param in the stream from which to read property values
+   * @throws IOException if there is an error in the stream
+   */
+  void read(ObjectInputStream in) throws IOException;
+
+  /**
+   * Creates a copy of this map. The method copies the values and optionally the references to super maps.
+   *
+   * @param copySuperMapRefs if true, copies the references to super maps
+   * @param recursive        if true, copies all child maps as well
+   * @return a copy of this map
+   * @since IDW 1.3.0
+   */
+  PropertyMap copy(boolean copySuperMapRefs, boolean recursive);
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapContainer.java b/src/net/infonode/properties/propertymap/PropertyMapContainer.java
new file mode 100644
index 0000000..25be7b4
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapContainer.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapContainer.java,v 1.5 2004/08/27 18:53:37 jesper Exp $
+package net.infonode.properties.propertymap;
+
+/**
+ * Base class for property classes that use a {@link PropertyMap}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class PropertyMapContainer {
+  private PropertyMap map;
+
+  /**
+   * Constructor.
+   *
+   * @param propertyMapGroup the property map group
+   */
+  public PropertyMapContainer(PropertyMapGroup propertyMapGroup) {
+    map = PropertyMapFactory.create(propertyMapGroup);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param map the property map
+   */
+  public PropertyMapContainer(PropertyMap map) {
+    this.map = map;
+  }
+
+  /**
+   * Returns the property map.
+   *
+   * @return the property map
+   */
+  public PropertyMap getMap() {
+    return map;
+  }
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapFactory.java b/src/net/infonode/properties/propertymap/PropertyMapFactory.java
new file mode 100644
index 0000000..f3a98a5
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapFactory.java,v 1.3 2004/08/27 18:53:37 jesper Exp $
+package net.infonode.properties.propertymap;
+
+/**
+ * Contains factory methods for {@link PropertyMap}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class PropertyMapFactory {
+  private PropertyMapFactory() {
+  }
+
+  /**
+   * Creates a property map that can contain values for properties in the given property group.
+   *
+   * @param propertyGroup the property group
+   * @return a new property map
+   */
+  public static PropertyMap create(PropertyMapGroup propertyGroup) {
+    return new PropertyMapImpl(propertyGroup);
+  }
+
+  /**
+   * Creates a property map with the same property group as <tt>inheritFrom</tt>.
+   * <tt>inheritFrom</tt> is added as a super map to the created property map.
+   *
+   * @param inheritFrom the super map
+   * @return a new property map
+   */
+  public static PropertyMap create(PropertyMap inheritFrom) {
+    return new PropertyMapImpl((PropertyMapImpl) inheritFrom);
+  }
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapGroup.java b/src/net/infonode/properties/propertymap/PropertyMapGroup.java
new file mode 100644
index 0000000..5436eef
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapGroup.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapGroup.java,v 1.3 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.propertymap;
+
+import net.infonode.properties.base.PropertyGroup;
+
+/**
+ * A property group containing properties for which values can be set in a property map.
+ * The property map group has a property map containing default values for the properties in this group.
+ * If no default value is set, the default value of the property will be used.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class PropertyMapGroup extends PropertyGroup {
+  private PropertyMapImpl defaultMap;
+
+  /**
+   * Constructor.
+   *
+   * @param name        the name of this group
+   * @param description the description for this group
+   */
+  public PropertyMapGroup(String name, String description) {
+    super(name, description);
+  }
+
+  /**
+   * Creates a group with a super group.
+   *
+   * @param superGroup  the super group from which to inherit properties
+   * @param name        the name of this group
+   * @param description the description for this group
+   */
+  public PropertyMapGroup(PropertyMapGroup superGroup, String name, String description) {
+    super(superGroup, name, description);
+  }
+
+  /**
+   * Returns the property map containing the default values for properties in this group.
+   *
+   * @return the property map containing the default values for properties in this group
+   */
+  public PropertyMap getDefaultMap() {
+    if (defaultMap == null)
+      defaultMap = new PropertyMapImpl(this);
+
+    return defaultMap;
+  }
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapImpl.java b/src/net/infonode/properties/propertymap/PropertyMapImpl.java
new file mode 100644
index 0000000..2b8a77a
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapImpl.java
@@ -0,0 +1,923 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapImpl.java,v 1.28 2005/12/04 13:46:06 jesper Exp $
+package net.infonode.properties.propertymap;
+
+import net.infonode.properties.base.Property;
+import net.infonode.properties.base.exception.InvalidPropertyException;
+import net.infonode.properties.propertymap.ref.*;
+import net.infonode.properties.propertymap.value.PropertyRefValue;
+import net.infonode.properties.propertymap.value.PropertyValue;
+import net.infonode.properties.propertymap.value.ValueDecoder;
+import net.infonode.properties.util.PropertyChangeListener;
+import net.infonode.properties.util.PropertyPath;
+import net.infonode.util.Printer;
+import net.infonode.util.Utils;
+import net.infonode.util.ValueChange;
+import net.infonode.util.collection.map.ConstVectorMap;
+import net.infonode.util.collection.map.MapAdapter;
+import net.infonode.util.collection.map.SingleValueMap;
+import net.infonode.util.collection.map.base.ConstMap;
+import net.infonode.util.collection.map.base.ConstMapIterator;
+import net.infonode.util.collection.map.base.MapIterator;
+import net.infonode.util.collection.notifymap.AbstractConstChangeNotifyMap;
+import net.infonode.util.collection.notifymap.ChangeNotifyMapWrapper;
+import net.infonode.util.collection.notifymap.ConstChangeNotifyMap;
+import net.infonode.util.collection.notifymap.ConstChangeNotifyVectorMap;
+import net.infonode.util.signal.Signal;
+import net.infonode.util.signal.SignalListener;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.28 $
+ */
+public class PropertyMapImpl implements PropertyMap {
+  private static final int SERIALIZE_VERSION = 1;
+
+  private class PropertyObjectMap extends AbstractConstChangeNotifyMap implements SignalListener {
+    private boolean listenerActive;
+
+    PropertyObjectMap() {
+    }
+
+    protected void listenerAdded() {
+      if (!listenerActive) {
+        listenerActive = true;
+        addInheritedReferences();
+        superMap.getChangeSignal().add(this);
+      }
+    }
+
+    public void signalEmitted(Signal signal, Object object) {
+      ConstMap changes = (ConstMap) object;
+      MapAdapter m = new MapAdapter();
+
+      for (ConstMapIterator iterator = changes.constIterator(); iterator.atEntry(); iterator.next()) {
+        Property property = (Property) iterator.getKey();
+
+        if (propertyGroup.hasProperty(property)) {
+          PropertyValue currentValue = (PropertyValue) values.get(property);
+
+          if (currentValue == null || currentValue.getParent() != null) {
+            ValueChange vc = (ValueChange) iterator.getValue();
+            PropertyValue superValue = (PropertyValue) vc.getNewValue();
+            PropertyValue newValue = superValue == null ? null : superValue.getSubValue(PropertyMapImpl.this);
+            internalSetValue(property, newValue);
+            m.put(property, new ValueChange(currentValue != null ? currentValue : vc.getOldValue(),
+                                            newValue != null ? newValue : vc.getNewValue()));
+          }
+        }
+      }
+
+      if (!m.isEmpty())
+        fireEntriesChanged(m);
+    }
+
+    protected void lastListenerRemoved() {
+      if (listenerActive) {
+        listenerActive = false;
+        superMap.getChangeSignal().remove(this);
+        removeInheritedReferences();
+      }
+    }
+
+    public boolean checkListeners(Set visited) {
+      for (Iterator it = getChangeSignalInternal().iterator(); it.hasNext();) {
+        Object l = it.next();
+
+        if (l instanceof PropertyRefValue) {
+          PropertyRefValue v = (PropertyRefValue) l;
+
+          if (v.getMap().checkListeners(visited))
+            return true;
+        }
+      }
+
+      return false;
+    }
+
+    public void updateListeners() {
+      for (Iterator it = getChangeSignalInternal().iterator(); it.hasNext();) {
+        if (!(it.next() instanceof PropertyRefValue)) {
+          return;
+        }
+      }
+
+      for (Iterator it = getChangeSignalInternal().iterator(); it.hasNext();) {
+        Object l = it.next();
+
+        if (l instanceof PropertyRefValue) {
+          PropertyRefValue v = (PropertyRefValue) l;
+
+          if (v.getMap().checkListeners(new HashSet())) {
+            return;
+          }
+        }
+      }
+
+      lastListenerRemoved();
+    }
+
+    private void addInheritedReferences() {
+      for (ConstMapIterator iterator = values.constIterator(); iterator.atEntry(); iterator.next()) {
+        Property property = (Property) iterator.getKey();
+        PropertyValue currentValue = (PropertyValue) values.get(property);
+        currentValue.updateListener(true);
+      }
+
+      for (ConstMapIterator iterator = superMap.constIterator(); iterator.atEntry(); iterator.next()) {
+        Property property = (Property) iterator.getKey();
+
+        if (propertyGroup.hasProperty(property)) {
+          PropertyValue currentValue = (PropertyValue) values.get(property);
+
+          if (currentValue == null || currentValue.getParent() != null) {
+            PropertyValue superValue = (PropertyValue) iterator.getValue();
+            PropertyValue newValue = superValue == null ? null : superValue.getSubValue(PropertyMapImpl.this);
+            internalSetValue(property, newValue);
+          }
+        }
+      }
+    }
+
+    private void removeInheritedReferences() {
+      ArrayList toBeRemoved = new ArrayList();
+
+      for (ConstMapIterator iterator = values.constIterator(); iterator.atEntry(); iterator.next()) {
+        Property property = (Property) iterator.getKey();
+        PropertyValue currentValue = (PropertyValue) values.get(property);
+
+        if (currentValue.getParent() != null) {
+          currentValue.unset();
+          toBeRemoved.add(property);
+        }
+        else {
+          currentValue.updateListener(false);
+        }
+      }
+
+      for (int i = 0; i < toBeRemoved.size(); i++) {
+        values.remove(toBeRemoved.get(i));
+      }
+    }
+
+    public Object get(Object key) {
+      return vectorMap.get(key);
+    }
+
+    public boolean containsKey(Object key) {
+      return vectorMap.containsKey(key);
+    }
+
+    public boolean containsValue(Object value) {
+      return vectorMap.containsValue(value);
+    }
+
+    public boolean isEmpty() {
+      return vectorMap.isEmpty();
+    }
+
+    public ConstMapIterator constIterator() {
+      return vectorMap.constIterator();
+    }
+
+    protected void fireEntriesChanged(ConstMap changes) {
+      super.fireEntriesChanged(changes);
+    }
+  }
+
+  private PropertyMapGroup propertyGroup;
+  private PropertyMapImpl parent;
+  private PropertyMapProperty property;
+
+  private ChangeNotifyMapWrapper values = new ChangeNotifyMapWrapper(new MapAdapter());
+  private ConstChangeNotifyVectorMap superMap = new ConstChangeNotifyVectorMap();
+  private ConstVectorMap vectorMap = new ConstVectorMap();
+  private PropertyObjectMap map = new PropertyObjectMap();
+
+  private ArrayList superMaps = new ArrayList(1);
+  private MapAdapter childMaps = new MapAdapter();
+
+  private HashMap propertyChangeListeners;
+  private ArrayList listeners;
+  private ArrayList treeListeners;
+
+  private SignalListener mapListener;
+
+  public PropertyMapImpl(PropertyMapGroup propertyGroup) {
+    this(propertyGroup, null);
+  }
+
+  public PropertyMapImpl(PropertyMapImpl inheritFrom) {
+    this(inheritFrom.getPropertyGroup(), inheritFrom);
+  }
+
+  public PropertyMapImpl(PropertyMapGroup propertyGroup, PropertyMapImpl superObject) {
+    this(propertyGroup, null, null);
+
+    if (superObject != null)
+      addSuperMap(superObject);
+  }
+
+  public PropertyMapImpl(PropertyMapImpl parent, PropertyMapProperty property) {
+    this(property.getPropertyMapGroup(), parent, property);
+  }
+
+  public PropertyMapImpl(PropertyMapGroup propertyGroup, PropertyMapImpl parent, PropertyMapProperty property) {
+    this.parent = parent;
+    this.property = property;
+    this.propertyGroup = propertyGroup;
+
+    Property[] properties = this.propertyGroup.getProperties();
+
+    for (int i = 0; i < properties.length; i++) {
+      if (properties[i] instanceof PropertyMapProperty) {
+        PropertyMapProperty p = (PropertyMapProperty) properties[i];
+        PropertyMapImpl propertyObject = new PropertyMapImpl(this, p);
+        childMaps.put(p, propertyObject);
+      }
+    }
+
+    vectorMap.addMap(values);
+    vectorMap.addMap(superMap);
+  }
+
+  private boolean hasTreeListener() {
+    return (treeListeners != null && treeListeners.size() > 0) || (parent != null && parent.hasTreeListener());
+  }
+
+  private boolean hasListener() {
+    return hasTreeListener() ||
+           (listeners != null && listeners.size() > 0) ||
+           (propertyChangeListeners != null && propertyChangeListeners.size() > 0);
+  }
+
+  private void updateListenerRecursive() {
+    updateListener();
+
+    for (ConstMapIterator iterator = childMaps.constIterator(); iterator.atEntry(); iterator.next())
+      ((PropertyMapImpl) iterator.getValue()).updateListenerRecursive();
+  }
+
+  private void updateListener() {
+    if (hasListener()) {
+      if (mapListener == null) {
+        mapListener = new SignalListener() {
+          public void signalEmitted(Signal signal, Object object) {
+            PropertyMapManager.getInstance().addMapChanges(PropertyMapImpl.this, (ConstMap) object);
+          }
+        };
+
+        map.getChangeSignal().add(mapListener);
+      }
+    }
+    else {
+      if (mapListener != null) {
+        map.getChangeSignal().remove(mapListener);
+        mapListener = null;
+        map.updateListeners();
+      }
+    }
+  }
+
+  private boolean checkListeners(Set visited) {
+    if (visited.contains(this))
+      return false;
+
+    visited.add(this);
+    return hasListener() || map.checkListeners(visited);
+  }
+
+  public ConstChangeNotifyMap getMap() {
+    return map;
+  }
+
+  public PropertyMap getSuperMap() {
+    return superMaps.size() == 0 ? null : (PropertyMap) superMaps.get(0);
+  }
+
+  public Object removeValue(Property property) throws InvalidPropertyException {
+    checkProperty(property);
+    PropertyValue value = (PropertyValue) values.get(property);
+
+    // Can't removeValue not set values or inherited reference values
+    if (value == null || value.getParent() != null)
+      return null;
+
+    values.remove(property);
+
+    PropertyMapManager.getInstance().beginBatch();
+
+    try {
+      firePropertyValueChanged(property, new ValueChange(value, getValue(property)));
+    }
+    finally {
+      PropertyMapManager.getInstance().endBatch();
+    }
+
+    return value.get(this);
+  }
+
+  private PropertyMapRef getPathFrom(PropertyMapImpl parentObject) {
+    if (parent == null)
+      return null;
+
+    if (parent == parentObject)
+      return new PropertyMapPropertyRef(property);
+
+    PropertyMapRef parentRef = parent.getPathFrom(parentObject);
+    return parentRef == null ? null : new CompositeMapRef(parentRef, new PropertyMapPropertyRef(property));
+  }
+
+  private PropertyMapRef getRelativePathTo(PropertyMapImpl propertyObject) {
+    PropertyMapRef ref = propertyObject == this ? ThisPropertyMapRef.INSTANCE : propertyObject.getPathFrom(this);
+    return ref == null ?
+           parent == null ?
+           null : new CompositeMapRef(ParentMapRef.INSTANCE, parent.getRelativePathTo(propertyObject)) :
+           ref;
+  }
+
+  public Object createRelativeRef(Property fromProperty, PropertyMap toObject, Property toProperty) {
+    PropertyValue value = setValue(fromProperty,
+                                   new PropertyRefValue(this,
+                                                        fromProperty,
+                                                        getRelativePathTo((PropertyMapImpl) toObject),
+                                                        toProperty,
+                                                        null));
+    return value == null ? null : value.getWithDefault(this);
+  }
+
+  public int getSuperMapCount() {
+    return superMaps.size();
+  }
+
+  public void addSuperMap(PropertyMap superMap) {
+    PropertyMapImpl superMapImpl = (PropertyMapImpl) superMap;
+
+/*    if (!propertyObjectImpl.propertyGroup.isA(propertyGroup))
+      throw new RuntimeException("Property group '" + propertyObjectImpl.propertyGroup + "�' can't be assigned to group '" + propertyGroup + "'!");
+      */
+    PropertyMapManager.getInstance().beginBatch();
+
+    try {
+      addSuperMap(0, superMapImpl);
+    }
+    finally {
+      PropertyMapManager.getInstance().endBatch();
+    }
+  }
+
+  public PropertyMap removeSuperMap() {
+    if (superMaps.size() > (parent == null ? 0 : parent.superMaps.size())) {
+      PropertyMapImpl object = (PropertyMapImpl) superMaps.get(0);
+      removeSuperMap(0);
+      return object;
+    }
+    else
+      return null;
+  }
+
+  public boolean removeSuperMap(PropertyMap superMap) {
+    if (superMaps.size() > (parent == null ? 0 : parent.superMaps.size())) {
+      int index = superMaps.indexOf(superMap);
+
+      if (index == -1)
+        return false;
+      else {
+        removeSuperMap(index);
+        return true;
+      }
+    }
+    else
+      return false;
+  }
+
+  public boolean replaceSuperMap(PropertyMap oldSuperMap, PropertyMap newSuperMap) {
+    if (oldSuperMap != newSuperMap && superMaps.size() > (parent == null ? 0 : parent.superMaps.size())) {
+      int index = superMaps.indexOf(oldSuperMap);
+
+      if (index == -1)
+        return false;
+      else {
+        PropertyMapManager.getInstance().beginBatch();
+
+        try {
+          removeSuperMap(index);
+          addSuperMap(index, (PropertyMapImpl) newSuperMap);
+        }
+        finally {
+          PropertyMapManager.getInstance().endBatch();
+        }
+
+        return true;
+      }
+    }
+    else
+      return false;
+  }
+
+  private void removeParentSuperMap(int parentIndex) {
+    removeSuperMap(superMaps.size() - parent.superMaps.size() - 1 + parentIndex);
+  }
+
+  private void removeSuperMap(int index) {
+    PropertyMapManager.getInstance().beginBatch();
+
+    try {
+      superMap.removeMap(index);
+      superMaps.remove(index);
+
+      for (ConstMapIterator iterator = childMaps.constIterator(); iterator.atEntry(); iterator.next()) {
+        ((PropertyMapImpl) iterator.getValue()).removeParentSuperMap(index);
+      }
+    }
+    finally {
+      PropertyMapManager.getInstance().endBatch();
+    }
+  }
+
+  private void addSuperMap(PropertyMapImpl propertyObjectImpl) {
+    addSuperMap(0, propertyObjectImpl);
+  }
+
+  private void addParentSuperMap(PropertyMapImpl propertyObjectImpl, int parentIndex) {
+    addSuperMap(superMaps.size() - parent.superMaps.size() + 1 + parentIndex, propertyObjectImpl);
+  }
+
+  private void addSuperMap(int index, PropertyMapImpl propertyObjectImpl) {
+    PropertyMapManager.getInstance().beginBatch();
+
+    try {
+      superMap.addMap(index, propertyObjectImpl.map);
+      superMaps.add(index, propertyObjectImpl);
+
+      for (ConstMapIterator iterator = childMaps.constIterator(); iterator.atEntry(); iterator.next()) {
+        ((PropertyMapImpl) iterator.getValue()).addParentSuperMap(
+            propertyObjectImpl.getChildMapImpl((PropertyMapProperty) iterator.getKey()), index);
+      }
+    }
+    finally {
+      PropertyMapManager.getInstance().endBatch();
+    }
+  }
+
+  public void addTreeListener(PropertyMapTreeListener listener) {
+    if (treeListeners == null)
+      treeListeners = new ArrayList(2);
+
+    treeListeners.add(listener);
+    updateListenerRecursive();
+  }
+
+  public void removeTreeListener(PropertyMapTreeListener listener) {
+    if (treeListeners != null) {
+      treeListeners.remove(listener);
+
+      if (treeListeners.size() == 0)
+        treeListeners = null;
+
+      updateListenerRecursive();
+    }
+  }
+
+  public void addListener(PropertyMapListener listener) {
+    if (listeners == null)
+      listeners = new ArrayList(2);
+
+    listeners.add(listener);
+    updateListener();
+  }
+
+  public void removeListener(PropertyMapListener listener) {
+    if (listeners != null) {
+      listeners.remove(listener);
+
+      if (listeners.size() == 0)
+        listeners = null;
+    }
+
+    updateListener();
+  }
+
+  public PropertyMapGroup getPropertyGroup() {
+    return propertyGroup;
+  }
+
+  public void addPropertyChangeListener(Property property, PropertyChangeListener listener) {
+    if (propertyChangeListeners == null)
+      propertyChangeListeners = new HashMap(4);
+
+    ArrayList list = (ArrayList) propertyChangeListeners.get(property);
+
+    if (list == null) {
+      list = new ArrayList(2);
+      propertyChangeListeners.put(property, list);
+    }
+
+    list.add(listener);
+    updateListener();
+  }
+
+  public void removePropertyChangeListener(Property property, PropertyChangeListener listener) {
+    if (propertyChangeListeners != null) {
+      ArrayList list = (ArrayList) propertyChangeListeners.get(property);
+
+      if (list == null)
+        return;
+
+      list.remove(listener);
+
+      if (list.isEmpty()) {
+        propertyChangeListeners.remove(property);
+
+        if (propertyChangeListeners.isEmpty())
+          propertyChangeListeners = null;
+      }
+
+      updateListener();
+    }
+  }
+
+  public PropertyMapImpl getParent() {
+    return parent;
+  }
+
+  public PropertyMapProperty getProperty() {
+    return property;
+  }
+
+  private void checkProperty(Property property) {
+    if (!propertyGroup.hasProperty(property))
+      throw new InvalidPropertyException(property,
+                                         "Property '" + property + "' not found in object '" + propertyGroup + "'!");
+  }
+
+  public PropertyMap getChildMap(PropertyMapProperty property) {
+    return getChildMapImpl(property);
+  }
+
+  public PropertyMapImpl getChildMapImpl(PropertyMapProperty property) {
+    checkProperty(property);
+    return (PropertyMapImpl) childMaps.get(property);
+  }
+
+  private PropertyValue getParentDefaultValue(PropertyPath path) {
+    PropertyValue value = parent == null ? null : parent.getParentDefaultValue(new PropertyPath(property, path));
+    return value == null ? ((PropertyMapImpl) propertyGroup.getDefaultMap()).getValue(path) : value;
+  }
+
+  public PropertyValue getValueWithDefault(Property property) {
+    PropertyValue value = getValue(property);
+    return value == null ? getParentDefaultValue(new PropertyPath(property)) : value;
+  }
+
+  private PropertyValue getValue(PropertyPath propertyPath) {
+    return propertyPath.getTail() == null ?
+           getValue(propertyPath.getProperty()) :
+           getChildMapImpl((PropertyMapProperty) propertyPath.getProperty()).getValue(propertyPath.getTail());
+  }
+
+  public PropertyValue getValue(Property property) {
+    checkProperty(property);
+    return (PropertyValue) map.get(property);
+  }
+
+  private PropertyValue internalSetValue(Property property, PropertyValue value) {
+    PropertyValue oldValue = (PropertyValue) (value == null ? values.remove(property) : values.put(property, value));
+
+    if (value != null)
+      value.updateListener(hasListener());
+
+    if (oldValue != null)
+      oldValue.unset();
+
+    return oldValue;
+  }
+
+  public PropertyValue setValue(Property property, PropertyValue value) {
+    checkProperty(property);
+    PropertyValue oldValue = getValue(property);
+    internalSetValue(property, value);
+
+    if (!Utils.equals(value, oldValue)) {
+      PropertyMapManager.getInstance().beginBatch();
+
+      try {
+        firePropertyValueChanged(property, new ValueChange(oldValue, value));
+      }
+      finally {
+        PropertyMapManager.getInstance().endBatch();
+      }
+    }
+
+    return oldValue;
+  }
+
+  public boolean valueIsSet(Property property) {
+    PropertyValue value = (PropertyValue) values.get(property);
+    return value != null && value.getParent() == null;
+  }
+
+  public void firePropertyValueChanged(Property property, ValueChange change) {
+    map.fireEntriesChanged(new SingleValueMap(property, change));
+  }
+
+  protected void firePropertyTreeValuesChanged(Map changes) {
+    if (treeListeners != null) {
+      PropertyMapTreeListener[] l = (PropertyMapTreeListener[]) treeListeners.toArray(
+          new PropertyMapTreeListener[treeListeners.size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].propertyValuesChanged(changes);
+    }
+  }
+
+  void firePropertyValuesChanged(Map changes) {
+    if (listeners != null) {
+      PropertyMapListener[] l = (PropertyMapListener[]) listeners.toArray(new PropertyMapListener[listeners.size()]);
+
+      for (int i = 0; i < l.length; i++)
+        l[i].propertyValuesChanged(this, changes);
+    }
+
+    if (propertyChangeListeners != null) {
+      for (Iterator iterator = changes.entrySet().iterator(); iterator.hasNext();) {
+        Map.Entry entry = (Map.Entry) iterator.next();
+        ArrayList list = (ArrayList) propertyChangeListeners.get(entry.getKey());
+
+        if (list != null) {
+          ValueChange vc = (ValueChange) entry.getValue();
+          PropertyChangeListener[] l = (PropertyChangeListener[]) list.toArray(new PropertyChangeListener[list.size()]);
+
+          for (int i = 0; i < l.length; i++)
+            l[i].propertyChanged((Property) entry.getKey(), this, vc.getOldValue(), vc.getNewValue());
+        }
+      }
+    }
+  }
+
+  public void dump() {
+    dump(new Printer(), new HashSet(4));
+  }
+
+  public void dump(Printer printer, Set printed) {
+    printed.add(this);
+
+    for (ConstMapIterator iterator = values.constIterator(); iterator.atEntry(); iterator.next()) {
+      printer.println(iterator.getKey() + " = " + iterator.getValue());
+    }
+
+    if (!values.isEmpty())
+      printer.println();
+
+    for (int i = 0; i < superMaps.size(); i++) {
+/*      if (printed.contains(superMaps.get(i)))
+        continue;
+*/
+      printer.println("Super Object " + (i + 1) + ':');
+      printer.beginSection();
+      ((PropertyMapImpl) superMaps.get(i)).dump(printer, printed);
+      printer.endSection();
+      printer.println();
+    }
+
+    for (ConstMapIterator iterator = childMaps.constIterator(); iterator.atEntry(); iterator.next()) {
+      printer.println(iterator.getKey() + ":");
+      printer.beginSection();
+      ((PropertyMapImpl) iterator.getValue()).dump(printer, printed);
+      printer.endSection();
+      printer.println();
+    }
+  }
+
+  public void dumpSuperMaps(Printer printer) {
+    printer.println(System.identityHashCode(this) + ":" + this);
+
+    for (int i = 0; i < superMaps.size(); i++) {
+//      if (superMap.getMap(i) != ((PropertyMapImpl) superMaps.get(i)).map)
+//        System.out.println("Error!");
+
+      printer.beginSection();
+      ((PropertyMapImpl) superMaps.get(i)).dumpSuperMaps(printer);
+      printer.endSection();
+    }
+
+  }
+
+  public void clear(boolean recursive) {
+    PropertyMapManager.getInstance().beginBatch();
+
+    try {
+      doClear(recursive);
+    }
+    finally {
+      PropertyMapManager.getInstance().endBatch();
+    }
+  }
+
+  private void doClear(boolean recursive) {
+    ArrayList items = new ArrayList(10);
+
+    for (MapIterator iterator = values.iterator(); iterator.atEntry(); iterator.next()) {
+      PropertyValue value = (PropertyValue) iterator.getValue();
+
+      if (value.getParent() == null)
+        items.add(iterator.getKey());
+    }
+
+    for (int i = 0; i < items.size(); i++)
+      removeValue((Property) items.get(i));
+
+    if (recursive) {
+      for (ConstMapIterator iterator = childMaps.constIterator(); iterator.atEntry(); iterator.next()) {
+        ((PropertyMapImpl) iterator.getValue()).doClear(recursive);
+      }
+    }
+  }
+
+  public boolean isEmpty(boolean recursive) {
+    for (ConstMapIterator iterator = values.constIterator(); iterator.atEntry(); iterator.next()) {
+      PropertyValue value = (PropertyValue) iterator.getValue();
+
+      if (value.getParent() == null)
+        return false;
+    }
+
+    if (recursive) {
+      for (ConstMapIterator iterator = childMaps.constIterator(); iterator.atEntry(); iterator.next()) {
+        if (!((PropertyMapImpl) iterator.getValue()).isEmpty(recursive))
+          return false;
+      }
+    }
+
+    return true;
+  }
+
+  private void doRead(ObjectInputStream in) throws IOException {
+    while (in.readBoolean()) {
+      String propertyName = in.readUTF();
+      Property property = getPropertyGroup().getProperty(propertyName);
+      PropertyValue value = ValueDecoder.decode(in, this, property);
+
+      if (property != null && value != null)
+        setValue(property, value);
+    }
+
+    while (in.readBoolean()) {
+      PropertyMapProperty property = (PropertyMapProperty) getPropertyGroup().getProperty(in.readUTF());
+      getChildMapImpl(property).doRead(in);
+    }
+  }
+
+  public void write(ObjectOutputStream out, boolean recursive) throws IOException {
+    out.writeInt(SERIALIZE_VERSION);
+    doWrite(out, recursive);
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    write(out, true);
+  }
+
+  private void doWrite(ObjectOutputStream out, boolean recursive) throws IOException {
+    for (ConstMapIterator iterator = values.constIterator(); iterator.atEntry(); iterator.next()) {
+      PropertyValue value = (PropertyValue) iterator.getValue();
+      if (value.getParent() == null && value.isSerializable()) {
+        out.writeBoolean(true);
+        out.writeUTF(((Property) iterator.getKey()).getName());
+        value.write(out);
+      }
+    }
+
+    out.writeBoolean(false);
+
+    if (recursive) {
+      for (ConstMapIterator iterator = childMaps.constIterator(); iterator.atEntry(); iterator.next()) {
+        if (!((PropertyMapImpl) iterator.getValue()).isEmpty(true)) {
+          out.writeBoolean(true);
+          out.writeUTF(((Property) iterator.getKey()).getName());
+          ((PropertyMapImpl) iterator.getValue()).doWrite(out, recursive);
+        }
+      }
+    }
+
+    out.writeBoolean(false);
+  }
+
+  public void read(ObjectInputStream in) throws IOException {
+    PropertyMapManager.getInstance().beginBatch();
+
+    try {
+      int version = in.readInt();
+
+      if (version > SERIALIZE_VERSION)
+        throw new IOException("Can't read object because serialized version is newer than current version!");
+
+      doRead(in);
+    }
+    finally {
+      PropertyMapManager.getInstance().endBatch();
+    }
+  }
+
+  public static void skip(ObjectInputStream in) throws IOException {
+    int version = in.readInt();
+
+    if (version > SERIALIZE_VERSION)
+      throw new IOException("Can't read object because serialized version is newer than current version!");
+
+    doSkip(in);
+  }
+
+  private static void doSkip(ObjectInputStream in) throws IOException {
+    while (in.readBoolean()) {
+      in.readUTF();
+      ValueDecoder.skip(in);
+    }
+
+    while (in.readBoolean()) {
+      in.readUTF();
+      doSkip(in);
+    }
+  }
+
+  private boolean doValuesEqual(PropertyMapImpl propertyObject, boolean recursive) {
+    for (ConstMapIterator iterator = map.constIterator(); iterator.atEntry(); iterator.next()) {
+      Property property = (Property) iterator.getKey();
+
+      if (!Utils.equals(((PropertyValue) iterator.getValue()).get(this), propertyObject.getValue(property).get(this)))
+        return false;
+    }
+
+    if (recursive) {
+      for (ConstMapIterator iterator = childMaps.constIterator(); iterator.atEntry(); iterator.next()) {
+        PropertyMapProperty property = (PropertyMapProperty) iterator.getKey();
+
+        if (!((PropertyMapImpl) iterator.getValue()).doValuesEqual(propertyObject.getChildMapImpl(property),
+                                                                   recursive))
+          return false;
+      }
+    }
+
+    return true;
+  }
+
+  public boolean valuesEqualTo(PropertyMap propertyObject, boolean recursive) {
+    return doValuesEqual((PropertyMapImpl) propertyObject, recursive);
+  }
+
+  public PropertyMap copy(boolean copySuperMaps, boolean recursive) {
+    PropertyMapImpl map = new PropertyMapImpl(propertyGroup);
+    doCopy(map, copySuperMaps, recursive, true);
+    return map;
+  }
+
+  private void doCopy(PropertyMapImpl map, boolean copySuperMaps, boolean recursive, boolean topMap) {
+    for (ConstMapIterator iterator = values.constIterator(); iterator.atEntry(); iterator.next()) {
+      PropertyValue value = (PropertyValue) iterator.getValue();
+
+      if (value.getParent() == null) {
+        map.values.put(iterator.getKey(), value.copyTo(map));
+      }
+    }
+
+    if (copySuperMaps) {
+      for (int i = 0; i < (topMap ? superMaps.size() : superMaps.size() - parent.superMaps.size()); i++)
+        map.addSuperMap((PropertyMapImpl) superMaps.get(i));
+    }
+
+    if (recursive) {
+      for (ConstMapIterator iterator = childMaps.constIterator(); iterator.atEntry(); iterator.next()) {
+        ((PropertyMapImpl) iterator.getValue()).doCopy(
+            (PropertyMapImpl) map.getChildMap((PropertyMapProperty) iterator.getKey()),
+            copySuperMaps,
+            recursive,
+            false);
+      }
+    }
+  }
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapListener.java b/src/net/infonode/properties/propertymap/PropertyMapListener.java
new file mode 100644
index 0000000..a0389ad
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapListener.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapListener.java,v 1.3 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.propertymap;
+
+import java.util.Map;
+
+/**
+ * Listener interface for property value changes in a property map.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public interface PropertyMapListener {
+  /**
+   * Invoked when one or more property values have changed in a property map.
+   *
+   * @param propertyMap the property map where the changes occured
+   * @param changes     an unmodifiable map containing {@link net.infonode.properties.base.Property}'s as keys and
+   *                    {@link net.infonode.util.ValueChange}'s as values
+   */
+  void propertyValuesChanged(PropertyMap propertyMap, Map changes);
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapManager.java b/src/net/infonode/properties/propertymap/PropertyMapManager.java
new file mode 100644
index 0000000..3b17d82
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapManager.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapManager.java,v 1.16 2005/12/04 13:46:06 jesper Exp $
+package net.infonode.properties.propertymap;
+
+import net.infonode.properties.propertymap.value.PropertyValue;
+import net.infonode.util.Utils;
+import net.infonode.util.ValueChange;
+import net.infonode.util.collection.map.base.ConstMap;
+import net.infonode.util.collection.map.base.ConstMapIterator;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Utility class for performing multiple modifications to {@link PropertyMap}'s and merging change notifications to
+ * optimize performance.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.16 $
+ */
+public class PropertyMapManager {
+  private static final PropertyMapManager INSTANCE = new PropertyMapManager();
+
+  private HashMap changes;
+  private int batchCounter;
+
+  /**
+   * Returns the only instance of this class.
+   *
+   * @return the only instance of this class
+   */
+  public static PropertyMapManager getInstance() {
+    return INSTANCE;
+  }
+
+  void addMapChanges(PropertyMapImpl propertyMap, ConstMap mapChanges) {
+    HashMap map = (HashMap) changes.get(propertyMap);
+
+    if (map == null) {
+      map = new HashMap();
+      changes.put(propertyMap, map);
+    }
+
+    for (ConstMapIterator iterator = mapChanges.constIterator(); iterator.atEntry(); iterator.next()) {
+      ValueChange vc = (ValueChange) iterator.getValue();
+      Object key = iterator.getKey();
+      Object newValue = vc.getNewValue() == null ?
+                        null : ((PropertyValue) vc.getNewValue()).getWithDefault(propertyMap);
+      Object value = map.get(key);
+      Object oldValue = value == null ?
+                        vc.getOldValue() == null ?
+                        null : ((PropertyValue) vc.getOldValue()).getWithDefault(propertyMap) :
+                        ((ValueChange) value).getOldValue();
+
+      if (!Utils.equals(oldValue, newValue))
+        map.put(iterator.getKey(), new ValueChange(oldValue, newValue));
+      else if (value != null)
+        map.remove(key);
+    }
+  }
+
+  /**
+   * Executes a method inside a {@link #beginBatch()} - {@link #endBatch()} pair. See {@link #beginBatch()} for
+   * more information. It's safe to call other batch methods from inside {@link Runnable#run}.
+   *
+   * @param runnable the runnable to invoke
+   */
+  public static void runBatch(Runnable runnable) {
+    getInstance().beginBatch();
+
+    try {
+      runnable.run();
+    }
+    finally {
+      getInstance().endBatch();
+    }
+  }
+
+  /**
+   * Begins a batch operation. This stores and merges all change notifications occuring in all property maps until
+   * {@link #endBatch} is called. Each call to this method MUST be followed by a call to {@link #endBatch}.
+   * This method can be called an unlimited number of times without calling {@link #endBatch} in between, but each
+   * call must have a corresponding call to {@link #endBatch}. Only when exiting from the
+   * outermost {@link #endBatch()} the changes be propagated to the listeners.
+   */
+  public void beginBatch() {
+    if (batchCounter++ == 0)
+      changes = new HashMap();
+  }
+
+  private void addTreeChanges(PropertyMapImpl map, PropertyMapImpl modifiedMap, HashMap changes, HashMap treeChanges) {
+    HashMap changeMap = (HashMap) treeChanges.get(map);
+
+    if (changeMap == null) {
+      changeMap = new HashMap();
+      treeChanges.put(map, changeMap);
+    }
+
+    changeMap.put(modifiedMap, changes);
+
+    if (map.getParent() != null)
+      addTreeChanges(map.getParent(), modifiedMap, changes, treeChanges);
+  }
+
+  /**
+   * Ends a batch operation. See {@link #beginBatch()} for more information.
+   */
+  public void endBatch() {
+    if (--batchCounter == 0) {
+      HashMap treeChanges = new HashMap();
+      HashMap localChanges = changes;
+      changes = null;
+
+      for (Iterator iterator = localChanges.entrySet().iterator(); iterator.hasNext();) {
+        Map.Entry entry = (Map.Entry) iterator.next();
+        PropertyMapImpl object = (PropertyMapImpl) entry.getKey();
+        HashMap objectChanges = (HashMap) entry.getValue();
+
+        if (!objectChanges.isEmpty()) {
+          object.firePropertyValuesChanged(Collections.unmodifiableMap(objectChanges));
+          addTreeChanges(object, object, objectChanges, treeChanges);
+        }
+      }
+
+      for (Iterator iterator = treeChanges.entrySet().iterator(); iterator.hasNext();) {
+        Map.Entry entry = (Map.Entry) iterator.next();
+        PropertyMapImpl object = (PropertyMapImpl) entry.getKey();
+        HashMap objectChanges = (HashMap) entry.getValue();
+
+        if (!objectChanges.isEmpty())
+          object.firePropertyTreeValuesChanged(Collections.unmodifiableMap(objectChanges));
+      }
+    }
+  }
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapProperty.java b/src/net/infonode/properties/propertymap/PropertyMapProperty.java
new file mode 100644
index 0000000..6da9c3a
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapProperty.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapProperty.java,v 1.4 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.propertymap;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.types.PropertyGroupProperty;
+
+/**
+ * An immutable property which has {@link PropertyMap}'s as values.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class PropertyMapProperty extends PropertyGroupProperty {
+  /**
+   * Constructor.
+   *
+   * @param group         the property group
+   * @param name          the property name
+   * @param description   the property description
+   * @param propertyGroup property maps for this property group can be values for this property
+   */
+  public PropertyMapProperty(PropertyGroup group, String name, String description, PropertyMapGroup propertyGroup) {
+    super(group, name, PropertyMap.class, description, PropertyMapValueHandler.INSTANCE, propertyGroup);
+  }
+
+  /**
+   * Returns the property group which property maps can be used as values for this property.
+   *
+   * @return the property group which property maps can be used as values for this property
+   */
+  public PropertyMapGroup getPropertyMapGroup() {
+    return (PropertyMapGroup) getPropertyGroup();
+  }
+
+  public boolean isMutable() {
+    return false;
+  }
+
+  public Object getValue(Object object) {
+    return ((PropertyMapImpl) object).getChildMapImpl(this);
+  }
+
+  /**
+   * Return the property valueContainer value for this property in the value container.
+   *
+   * @param valueContainer the value container
+   * @return the property valueContainer value for this property in the value container
+   */
+  public PropertyMap get(Object valueContainer) {
+    return (PropertyMap) getValue(valueContainer);
+  }
+
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapTreeListener.java b/src/net/infonode/properties/propertymap/PropertyMapTreeListener.java
new file mode 100644
index 0000000..1835c25
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapTreeListener.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapTreeListener.java,v 1.4 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.propertymap;
+
+import java.util.Map;
+
+/**
+ * Listener interface for property value changes in a property map and it's child maps.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public interface PropertyMapTreeListener {
+  /**
+   * Invoked when one or more property value has been modified in one or more property maps.
+   *
+   * @param changes an unmodifiable {@link Map} containing {@link PropertyMap}'s as keys and
+   *                unmodifiable {@link Map}'s as values. The value Map's contains
+   *                {@link net.infonode.properties.base.Property}'s as keys and
+   *                {@link net.infonode.util.ValueChange}'s as values.
+   */
+  void propertyValuesChanged(Map changes);
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapUtil.java b/src/net/infonode/properties/propertymap/PropertyMapUtil.java
new file mode 100644
index 0000000..4f11e0d
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapUtil.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapUtil.java,v 1.4 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.propertymap;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * Property map utility methods.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @since IDW 1.3.0
+ */
+public final class PropertyMapUtil {
+  private PropertyMapUtil() {
+  }
+
+  /**
+   * Skips a property map in the stream.
+   *
+   * @param in the stream containing the property map
+   * @throws IOException if there is an error in the stream
+   */
+  public static void skipMap(ObjectInputStream in) throws IOException {
+    PropertyMapImpl.skip(in);
+  }
+
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapValueHandler.java b/src/net/infonode/properties/propertymap/PropertyMapValueHandler.java
new file mode 100644
index 0000000..8212ec3
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapValueHandler.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapValueHandler.java,v 1.3 2004/07/06 15:07:17 jesper Exp $
+package net.infonode.properties.propertymap;
+
+import net.infonode.properties.base.Property;
+import net.infonode.properties.base.exception.CantRemoveValueException;
+import net.infonode.properties.propertymap.value.PropertyValue;
+import net.infonode.properties.propertymap.value.SimplePropertyValue;
+import net.infonode.properties.types.PropertyGroupProperty;
+import net.infonode.properties.util.PropertyValueHandler;
+
+/**
+ * Property value handler for property maps.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class PropertyMapValueHandler implements PropertyValueHandler {
+  /**
+   * The instance of this class.
+   */
+  public static final PropertyMapValueHandler INSTANCE = new PropertyMapValueHandler();
+
+  /**
+   * Constructor
+   */
+  private PropertyMapValueHandler() {
+  }
+
+  public Object getValue(Property property, Object object) {
+    PropertyMapImpl propertyMap = (PropertyMapImpl) object;
+    PropertyValue value = propertyMap.getValueWithDefault(property);
+    return value == null ? null : value.getWithDefault(propertyMap);
+  }
+
+  public void setValue(Property property, Object object, Object value) {
+    ((PropertyMapImpl) object).setValue(property, new SimplePropertyValue(value));
+  }
+
+  public boolean getValueIsRemovable(Property property, Object object) {
+    return !(property instanceof PropertyGroupProperty);
+  }
+
+  public void removeValue(Property property, Object object) {
+    if (property instanceof PropertyGroupProperty)
+      throw new CantRemoveValueException(property);
+
+    ((PropertyMapImpl) object).removeValue(property);
+  }
+
+  public boolean getValueIsSet(Property property, Object object) {
+    return (property instanceof PropertyGroupProperty) || ((PropertyMapImpl) object).valueIsSet(property);
+  }
+}
diff --git a/src/net/infonode/properties/propertymap/PropertyMapWeakListenerManager.java b/src/net/infonode/properties/propertymap/PropertyMapWeakListenerManager.java
new file mode 100644
index 0000000..ffe69d8
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/PropertyMapWeakListenerManager.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapWeakListenerManager.java,v 1.8 2005/03/09 16:57:26 jesper Exp $
+package net.infonode.properties.propertymap;
+
+import net.infonode.properties.base.Property;
+import net.infonode.properties.util.PropertyChangeListener;
+
+import javax.swing.*;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * Handles weak {@link PropertyMap} listeners which are garbage collected and removed from the {@link PropertyMap}
+ * object on which it listens when there are no strong or soft references to the listeners.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ * @since IDW 1.2.0
+ */
+public class PropertyMapWeakListenerManager {
+  private PropertyMapWeakListenerManager() {
+  }
+
+  private static class ListenerRef extends WeakReference {
+    private PropertyMap map;
+
+    protected ListenerRef(Object referent, ReferenceQueue q, PropertyMap map) {
+      super(referent, q);
+      this.map = map;
+    }
+
+    public PropertyMap getMap() {
+      return map;
+    }
+
+    public void removeFromMap() {
+      map = null;
+    }
+
+  }
+
+  private static class MapListenerRef extends ListenerRef implements PropertyMapListener {
+    MapListenerRef(PropertyMapListener referent, ReferenceQueue q, PropertyMap map) {
+      super(referent, q, map);
+      map.addListener(this);
+    }
+
+    public void removeFromMap() {
+      getMap().removeListener(this);
+      super.removeFromMap();
+    }
+
+    public void propertyValuesChanged(PropertyMap propertyMap, Map changes) {
+      PropertyMapListener l = (PropertyMapListener) get();
+
+      if (l != null)
+        l.propertyValuesChanged(propertyMap, changes);
+    }
+  }
+
+  private static class PropertyChangeListenerRef extends ListenerRef implements PropertyChangeListener {
+    private Property property;
+
+    PropertyChangeListenerRef(PropertyChangeListener referent, ReferenceQueue q, PropertyMap map, Property property) {
+      super(referent, q, map);
+      this.property = property;
+      map.addPropertyChangeListener(property, this);
+    }
+
+    public Property getProperty() {
+      return property;
+    }
+
+    public void removeFromMap() {
+      getMap().removePropertyChangeListener(property, this);
+      super.removeFromMap();
+    }
+
+    public void propertyChanged(Property property, Object valueContainer, Object oldValue, Object newValue) {
+      PropertyChangeListener l = (PropertyChangeListener) get();
+
+      if (l != null)
+        l.propertyChanged(property, valueContainer, oldValue, newValue);
+    }
+  }
+
+  private static class TreeListenerRef extends ListenerRef implements PropertyMapTreeListener {
+    TreeListenerRef(PropertyMapTreeListener referent, ReferenceQueue q, PropertyMap map) {
+      super(referent, q, map);
+      map.addTreeListener(this);
+    }
+
+    public void removeFromMap() {
+      getMap().removeTreeListener(this);
+      super.removeFromMap();
+    }
+
+    public void propertyValuesChanged(Map changes) {
+      PropertyMapTreeListener l = (PropertyMapTreeListener) get();
+
+      if (l != null)
+        l.propertyValuesChanged(changes);
+    }
+  }
+
+  private static WeakHashMap listenerMap = new WeakHashMap();
+  private static WeakHashMap propertyChangeListenerMap = new WeakHashMap();
+  private static WeakHashMap treeListenerMap = new WeakHashMap();
+  private static ReferenceQueue refQueue = new ReferenceQueue();
+
+  private static ListenerRef ref;
+  private static Runnable refRemover = new Runnable() {
+    public void run() {
+      while (ref != null) {
+        ref.removeFromMap();
+        ref = (ListenerRef) refQueue.poll();
+      }
+    }
+  };
+
+  static {
+    Thread thread = new Thread(new Runnable() {
+      public void run() {
+        try {
+          while (true) {
+            ref = (ListenerRef) refQueue.remove();
+            SwingUtilities.invokeAndWait(refRemover);
+          }
+        }
+        catch (InterruptedException e) {
+        }
+        catch (InvocationTargetException e) {
+        }
+      }
+    });
+    thread.setDaemon(true);
+    thread.start();
+  }
+
+  private static void addToMap(WeakHashMap map, Object key, Object value) {
+    ArrayList l = (ArrayList) map.get(key);
+
+    if (l == null) {
+      l = new ArrayList(2);
+      map.put(key, l);
+    }
+
+    l.add(value);
+  }
+
+  private static void removeFromMap(WeakHashMap map, Object key, PropertyMap propertyMap) {
+    ArrayList l = (ArrayList) map.get(key);
+
+    if (l != null) {
+      for (int i = 0; i < l.size(); i++) {
+        ListenerRef ref = (ListenerRef) l.get(i);
+
+        if (ref.getMap() == propertyMap) {
+          ref.removeFromMap();
+          l.remove(i);
+
+          if (l.size() == 0) {
+            map.remove(key);
+          }
+
+          return;
+        }
+      }
+    }
+  }
+
+  private static void removeFromMap(WeakHashMap map, Object key, PropertyMap propertyMap, Property property) {
+    ArrayList l = (ArrayList) map.get(key);
+
+    if (l != null) {
+      for (int i = 0; i < l.size(); i++) {
+        PropertyChangeListenerRef ref = (PropertyChangeListenerRef) l.get(i);
+
+        if (ref.getMap() == propertyMap && ref.getProperty() == property) {
+          ref.removeFromMap();
+          l.remove(i);
+
+          if (l.size() == 0) {
+            map.remove(key);
+          }
+
+          return;
+        }
+      }
+    }
+  }
+
+  /**
+   * Adds a weak listener to a {@link PropertyMap}.
+   *
+   * @param map      the {@link PropertyMap}
+   * @param listener the listener
+   */
+  public static void addWeakListener(PropertyMap map, PropertyMapListener listener) {
+    MapListenerRef l = new MapListenerRef(listener, refQueue, map);
+    addToMap(listenerMap, listener, l);
+  }
+
+  /**
+   * Adds a weak property change listener to a {@link PropertyMap}.
+   *
+   * @param map      the {@link PropertyMap}
+   * @param property the property to listen to changes on
+   * @param listener the listener
+   */
+  public static void addWeakPropertyChangeListener(PropertyMap map,
+                                                   Property property,
+                                                   PropertyChangeListener listener) {
+    PropertyChangeListenerRef l = new PropertyChangeListenerRef(listener, refQueue, map, property);
+    addToMap(propertyChangeListenerMap, listener, l);
+  }
+
+  /**
+   * Adds a weak tree listener to a {@link PropertyMap}.
+   *
+   * @param map      the {@link PropertyMap}
+   * @param listener the listener
+   */
+  public static void addWeakTreeListener(PropertyMap map, PropertyMapTreeListener listener) {
+    TreeListenerRef l = new TreeListenerRef(listener, refQueue, map);
+    addToMap(treeListenerMap, listener, l);
+  }
+
+  /**
+   * Removes a listener previously added with {@link #addWeakListener(PropertyMap, PropertyMapListener)}.
+   *
+   * @param map      the map on which the listener was added
+   * @param listener the listener
+   */
+  public static void removeWeakListener(PropertyMap map, PropertyMapListener listener) {
+    removeFromMap(listenerMap, listener, map);
+  }
+
+  /**
+   * Removes a listener previously added with
+   * {@link #addWeakPropertyChangeListener(PropertyMap, net.infonode.properties.base.Property, net.infonode.properties.util.PropertyChangeListener)}.
+   *
+   * @param map      the map on which the listener was added
+   * @param property the property on which the listener listens to changes
+   * @param listener the listener
+   */
+  public static void removeWeakPropertyChangeListener(PropertyMap map,
+                                                      Property property,
+                                                      PropertyChangeListener listener) {
+    removeFromMap(propertyChangeListenerMap, listener, map, property);
+  }
+
+  /**
+   * Removes a listener previously added with {@link #addWeakTreeListener(PropertyMap, PropertyMapTreeListener)}.
+   *
+   * @param map      the map on which the listener was added
+   * @param listener the listener
+   */
+  public static void removeWeakTreeListener(PropertyMap map, PropertyMapTreeListener listener) {
+    removeFromMap(treeListenerMap, listener, map);
+  }
+
+}
diff --git a/src/net/infonode/properties/propertymap/package.html b/src/net/infonode/properties/propertymap/package.html
new file mode 100644
index 0000000..a994d5a
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/package.html
@@ -0,0 +1,6 @@
+<html>
+<body>
+Property maps are used for storage of property values. The classes in this package are NOT thread safe, all method
+calls must be made in the AWT event thread.
+</body>
+</html>
diff --git a/src/net/infonode/properties/propertymap/ref/CompositeMapRef.java b/src/net/infonode/properties/propertymap/ref/CompositeMapRef.java
new file mode 100644
index 0000000..96ac48b
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/ref/CompositeMapRef.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: CompositeMapRef.java,v 1.5 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.propertymap.ref;
+
+import net.infonode.properties.propertymap.PropertyMapImpl;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class CompositeMapRef implements PropertyMapRef {
+  private PropertyMapRef ref1;
+  private PropertyMapRef ref2;
+
+  public CompositeMapRef(PropertyMapRef ref1, PropertyMapRef ref2) {
+    this.ref1 = ref1;
+    this.ref2 = ref2;
+  }
+
+  public PropertyMapImpl getMap(PropertyMapImpl object) {
+    return ref2.getMap(ref1.getMap(object));
+  }
+
+  public String toString() {
+    return ref1 + "." + ref2;
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(PropertyMapRefDecoder.COMPOSITE);
+    ref1.write(out);
+    ref2.write(out);
+  }
+
+  public static CompositeMapRef decode(ObjectInputStream in) throws IOException {
+    return new CompositeMapRef(PropertyMapRefDecoder.decode(in), PropertyMapRefDecoder.decode(in));
+  }
+}
diff --git a/src/net/infonode/properties/propertymap/ref/ParentMapRef.java b/src/net/infonode/properties/propertymap/ref/ParentMapRef.java
new file mode 100644
index 0000000..9d50b2a
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/ref/ParentMapRef.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ParentMapRef.java,v 1.5 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.propertymap.ref;
+
+import net.infonode.properties.propertymap.PropertyMapImpl;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class ParentMapRef implements PropertyMapRef {
+  public static final ParentMapRef INSTANCE = new ParentMapRef();
+
+  private ParentMapRef() {
+  }
+
+  public PropertyMapImpl getMap(PropertyMapImpl object) {
+    return object == null ? null : object.getParent();
+  }
+
+  public String toString() {
+    return "parent";
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(PropertyMapRefDecoder.PARENT);
+  }
+
+}
diff --git a/src/net/infonode/properties/propertymap/ref/PropertyMapPropertyRef.java b/src/net/infonode/properties/propertymap/ref/PropertyMapPropertyRef.java
new file mode 100644
index 0000000..b30b845
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/ref/PropertyMapPropertyRef.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapPropertyRef.java,v 1.8 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.propertymap.ref;
+
+import net.infonode.properties.propertymap.PropertyMapImpl;
+import net.infonode.properties.propertymap.PropertyMapProperty;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class PropertyMapPropertyRef implements PropertyMapRef {
+  private String propertyName;
+
+  private PropertyMapPropertyRef(String propertyName) {
+    this.propertyName = propertyName;
+  }
+
+  public PropertyMapPropertyRef(PropertyMapProperty property) {
+    propertyName = property.getName();
+  }
+
+  public PropertyMapImpl getMap(PropertyMapImpl object) {
+    return object == null ?
+           null : object.getChildMapImpl((PropertyMapProperty) object.getPropertyGroup().getProperty(propertyName));
+  }
+
+  public String toString() {
+    return "(property '" + propertyName + "')";
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(PropertyMapRefDecoder.PROPERTY_OBJECT_PROPERTY);
+    out.writeUTF(propertyName);
+  }
+
+  public static PropertyMapRef decode(ObjectInputStream in) throws IOException {
+    return new PropertyMapPropertyRef(in.readUTF());
+  }
+}
diff --git a/src/net/infonode/properties/propertymap/ref/PropertyMapRef.java b/src/net/infonode/properties/propertymap/ref/PropertyMapRef.java
new file mode 100644
index 0000000..70dca18
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/ref/PropertyMapRef.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapRef.java,v 1.6 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.propertymap.ref;
+
+import net.infonode.properties.propertymap.PropertyMapImpl;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public interface PropertyMapRef {
+  PropertyMapImpl getMap(PropertyMapImpl map);
+
+  void write(ObjectOutputStream out) throws IOException;
+}
diff --git a/src/net/infonode/properties/propertymap/ref/PropertyMapRefDecoder.java b/src/net/infonode/properties/propertymap/ref/PropertyMapRefDecoder.java
new file mode 100644
index 0000000..6bdaa67
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/ref/PropertyMapRefDecoder.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyMapRefDecoder.java,v 1.4 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.propertymap.ref;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class PropertyMapRefDecoder {
+  public static final int PARENT = 0;
+  public static final int THIS = 1;
+  public static final int PROPERTY_OBJECT_PROPERTY = 2;
+  public static final int COMPOSITE = 3;
+
+  private PropertyMapRefDecoder() {
+  }
+
+  public static PropertyMapRef decode(ObjectInputStream in) throws IOException {
+    int type = in.readInt();
+
+    switch (type) {
+      case PARENT:
+        return ParentMapRef.INSTANCE;
+
+      case THIS:
+        return ThisPropertyMapRef.INSTANCE;
+
+      case PROPERTY_OBJECT_PROPERTY:
+        return PropertyMapPropertyRef.decode(in);
+
+      case COMPOSITE:
+        return CompositeMapRef.decode(in);
+
+      default:
+        throw new IOException("Invalid property object ref type!");
+    }
+  }
+}
diff --git a/src/net/infonode/properties/propertymap/ref/ThisPropertyMapRef.java b/src/net/infonode/properties/propertymap/ref/ThisPropertyMapRef.java
new file mode 100644
index 0000000..fe50d8f
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/ref/ThisPropertyMapRef.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ThisPropertyMapRef.java,v 1.5 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.propertymap.ref;
+
+import net.infonode.properties.propertymap.PropertyMapImpl;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class ThisPropertyMapRef implements PropertyMapRef {
+  public static final ThisPropertyMapRef INSTANCE = new ThisPropertyMapRef();
+
+  private ThisPropertyMapRef() {
+  }
+
+  public PropertyMapImpl getMap(PropertyMapImpl object) {
+    return object;
+  }
+
+  public String toString() {
+    return "this";
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(PropertyMapRefDecoder.THIS);
+  }
+}
diff --git a/src/net/infonode/properties/propertymap/value/PropertyRefValue.java b/src/net/infonode/properties/propertymap/value/PropertyRefValue.java
new file mode 100644
index 0000000..3fad449
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/value/PropertyRefValue.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyRefValue.java,v 1.17 2005/12/04 13:46:06 jesper Exp $
+package net.infonode.properties.propertymap.value;
+
+import net.infonode.properties.base.Property;
+import net.infonode.properties.base.exception.InvalidPropertyTypeException;
+import net.infonode.properties.propertymap.PropertyMapImpl;
+import net.infonode.properties.propertymap.ref.PropertyMapRef;
+import net.infonode.properties.propertymap.ref.PropertyMapRefDecoder;
+import net.infonode.util.Printer;
+import net.infonode.util.ValueChange;
+import net.infonode.util.collection.map.base.ConstMap;
+import net.infonode.util.signal.Signal;
+import net.infonode.util.signal.SignalListener;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.17 $
+ */
+public class PropertyRefValue implements PropertyValue, SignalListener {
+  private PropertyMapImpl map;
+  private Property property;
+  private PropertyMapRef propertyObjectRef;
+  private Property propertyRef;
+  private PropertyRefValue parentRef;
+
+  public PropertyRefValue(PropertyMapImpl map, Property property, PropertyMapRef propertyObjectRef,
+                          Property propertyRef, PropertyRefValue parentRef) {
+    if (!property.getType().isAssignableFrom(propertyRef.getType()))
+      throw new InvalidPropertyTypeException(property,
+                                             propertyRef,
+                                             "Can't create reference from Property '" + property + "' to property '" +
+                                             propertyRef +
+                                             "' because they are of incompatible types!");
+
+    this.map = map;
+    this.property = property;
+    this.propertyObjectRef = propertyObjectRef;
+    this.propertyRef = propertyRef;
+    this.parentRef = parentRef;
+  }
+
+  public Property getProperty() {
+    return property;
+  }
+
+  public PropertyMapImpl getMap() {
+    return map;
+  }
+
+  public void updateListener(boolean enable) {
+    if (enable)
+      propertyObjectRef.getMap(map).getMap().getChangeSignal().add(this);
+    else
+      propertyObjectRef.getMap(map).getMap().getChangeSignal().remove(this);
+  }
+
+  public PropertyValue getParent() {
+    return parentRef;
+  }
+
+  public Object get(PropertyMapImpl object) {
+    PropertyMapImpl o = propertyObjectRef.getMap(object);
+    PropertyValue v = (o == null ? propertyObjectRef.getMap(map) : o).getValue(propertyRef);
+    return v == null ? null : v.get(o);
+  }
+
+  public Object getWithDefault(PropertyMapImpl object) {
+    PropertyMapImpl o = propertyObjectRef.getMap(object);
+    PropertyValue v = (o == null ? propertyObjectRef.getMap(map) : o).getValueWithDefault(propertyRef);
+    return v == null ? null : v.getWithDefault(o);
+  }
+
+  public PropertyValue getSubValue(PropertyMapImpl object) {
+    PropertyMapImpl newObject = propertyObjectRef.getMap(object);
+
+    if (newObject == null)
+      return null;
+
+    if (!newObject.getPropertyGroup().hasProperty(propertyRef))
+      return null;
+
+    return new PropertyRefValue(object, property, propertyObjectRef, propertyRef, this);
+  }
+
+  public void unset() {
+    propertyObjectRef.getMap(map).getMap().getChangeSignal().remove(this);
+  }
+
+  public void signalEmitted(Signal signal, Object object) {
+    ConstMap changes = (ConstMap) object;
+    ValueChange vc = (ValueChange) changes.get(propertyRef);
+
+    if (vc != null)
+      map.firePropertyValueChanged(property, new ValueChange(vc.getOldValue(), this));
+  }
+
+  public String toString() {
+    return "ref -> " + propertyObjectRef + '.' + propertyRef;
+  }
+
+  public void dump(Printer printer) {
+    printer.println(toString());
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(ValueDecoder.REF);
+    propertyObjectRef.write(out);
+    out.writeUTF(propertyRef.getName());
+  }
+
+  public boolean isSerializable() {
+    return true;
+  }
+
+  public static PropertyValue decode(ObjectInputStream in, PropertyMapImpl propertyObject, Property property) throws IOException {
+    PropertyMapRef ref = PropertyMapRefDecoder.decode(in);
+    String propertyName = in.readUTF();
+
+    if (property == null || ref == null)
+      return null;
+
+    Property refProperty = ref.getMap(propertyObject).getPropertyGroup().getProperty(propertyName);
+
+    if (refProperty == null)
+      return null;
+
+    return new PropertyRefValue(propertyObject, property, ref, refProperty, null);
+  }
+
+  public static void skip(ObjectInputStream in) throws IOException {
+    PropertyMapRefDecoder.decode(in);
+    in.readUTF();
+  }
+
+  public PropertyValue copyTo(PropertyMapImpl propertyMap) {
+    return new PropertyRefValue(propertyMap, property, propertyObjectRef, propertyRef, null);
+  }
+
+}
diff --git a/src/net/infonode/properties/propertymap/value/PropertyValue.java b/src/net/infonode/properties/propertymap/value/PropertyValue.java
new file mode 100644
index 0000000..5e596ab
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/value/PropertyValue.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyValue.java,v 1.10 2005/03/17 16:15:32 jesper Exp $
+package net.infonode.properties.propertymap.value;
+
+import net.infonode.properties.propertymap.PropertyMapImpl;
+import net.infonode.util.Printer;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ */
+public interface PropertyValue {
+  Object get(PropertyMapImpl map);
+
+  Object getWithDefault(PropertyMapImpl object);
+
+  PropertyValue getSubValue(PropertyMapImpl object);
+
+  void unset();
+
+  PropertyValue getParent();
+
+  void dump(Printer printer);
+
+  void write(ObjectOutputStream out) throws IOException;
+
+  void updateListener(boolean enable);
+
+  boolean isSerializable();
+
+  PropertyValue copyTo(PropertyMapImpl propertyMap);
+}
diff --git a/src/net/infonode/properties/propertymap/value/SimplePropertyValue.java b/src/net/infonode/properties/propertymap/value/SimplePropertyValue.java
new file mode 100644
index 0000000..f72ac58
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/value/SimplePropertyValue.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SimplePropertyValue.java,v 1.14 2005/03/17 16:15:32 jesper Exp $
+package net.infonode.properties.propertymap.value;
+
+import net.infonode.properties.propertymap.PropertyMapImpl;
+import net.infonode.util.Printer;
+import net.infonode.util.Utils;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.14 $
+ */
+public class SimplePropertyValue implements PropertyValue {
+  private final Object value;
+
+  public SimplePropertyValue(Object value) {
+    this.value = value;
+  }
+
+  public void updateListener(boolean enable) {
+  }
+
+  public PropertyValue getParent() {
+    return null;
+  }
+
+  public Object get(PropertyMapImpl object) {
+    return value;
+  }
+
+  public Object getWithDefault(PropertyMapImpl object) {
+    return value;
+  }
+
+  public PropertyValue getSubValue(PropertyMapImpl object) {
+    return null;
+  }
+
+  public void unset() {
+  }
+
+  public String toString() {
+    return String.valueOf(value);
+  }
+
+  public void dump(Printer printer) {
+    printer.println(toString());
+  }
+
+  public boolean equals(Object obj) {
+    return obj != null &&
+           obj instanceof SimplePropertyValue &&
+           Utils.equals(((SimplePropertyValue) obj).value, value);
+  }
+
+  public int hashCode() {
+    return value.hashCode();
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(ValueDecoder.SIMPLE);
+    out.writeObject(value);
+  }
+
+  public boolean isSerializable() {
+    return value instanceof Serializable;
+  }
+
+  public static PropertyValue decode(ObjectInputStream in) throws IOException {
+    try {
+      return new SimplePropertyValue(in.readObject());
+    }
+    catch (ClassNotFoundException e) {
+      throw new IOException(e.getMessage());
+    }
+  }
+
+  public static void skip(ObjectInputStream in) throws IOException {
+    try {
+      in.readObject();
+    }
+    catch (ClassNotFoundException e) {
+      throw new IOException(e.getMessage());
+    }
+  }
+
+  public PropertyValue copyTo(PropertyMapImpl propertyMap) {
+    return this;
+  }
+
+}
diff --git a/src/net/infonode/properties/propertymap/value/ValueDecoder.java b/src/net/infonode/properties/propertymap/value/ValueDecoder.java
new file mode 100644
index 0000000..95a5a69
--- /dev/null
+++ b/src/net/infonode/properties/propertymap/value/ValueDecoder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ValueDecoder.java,v 1.7 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.propertymap.value;
+
+import net.infonode.properties.base.Property;
+import net.infonode.properties.propertymap.PropertyMapImpl;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class ValueDecoder {
+  public static final int SIMPLE = 0;
+  public static final int REF = 1;
+
+  private ValueDecoder() {
+  }
+
+  public static PropertyValue decode(ObjectInputStream in, PropertyMapImpl propertyObject, Property property) throws IOException {
+    int type = in.readInt();
+
+    switch (type) {
+      case SIMPLE:
+        return SimplePropertyValue.decode(in);
+
+      case REF:
+        return PropertyRefValue.decode(in, propertyObject, property);
+
+      default:
+        throw new IOException("Invalid value type!");
+    }
+  }
+
+  public static void skip(ObjectInputStream in) throws IOException {
+    int type = in.readInt();
+
+    switch (type) {
+      case SIMPLE:
+        SimplePropertyValue.skip(in);
+        break;
+
+      case REF:
+        PropertyRefValue.skip(in);
+        break;
+
+      default:
+        throw new IOException("Invalid value type!");
+    }
+  }
+}
diff --git a/src/net/infonode/properties/types/AlignmentProperty.java b/src/net/infonode/properties/types/AlignmentProperty.java
new file mode 100644
index 0000000..2b73736
--- /dev/null
+++ b/src/net/infonode/properties/types/AlignmentProperty.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AlignmentProperty.java,v 1.6 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.util.Alignment;
+
+/**
+ * A property of type {@link Alignment}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class AlignmentProperty extends EnumProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   * @param validValues  valid values for this property
+   */
+  public AlignmentProperty(PropertyGroup group,
+                           String name,
+                           String description,
+                           PropertyValueHandler valueHandler,
+                           Alignment[] validValues) {
+    super(group, name, Alignment.class, description, valueHandler, validValues);
+  }
+
+  /**
+   * Returns the alignment value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the alignment value of this property
+   */
+  public Alignment get(Object valueContainer) {
+    return (Alignment) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the alignment value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param alignment      the alignment value
+   */
+  public void set(Object valueContainer, Alignment alignment) {
+    setValue(valueContainer, alignment);
+  }
+}
diff --git a/src/net/infonode/properties/types/BooleanProperty.java b/src/net/infonode/properties/types/BooleanProperty.java
new file mode 100644
index 0000000..66eb43c
--- /dev/null
+++ b/src/net/infonode/properties/types/BooleanProperty.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BooleanProperty.java,v 1.4 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A boolean property.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class BooleanProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public BooleanProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueHandler) {
+    super(group, name, Boolean.class, description, valueHandler);
+  }
+
+  /**
+   * Returns the boolean value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the boolean value of this property
+   */
+  public boolean get(Object valueContainer) {
+    Object value = getValue(valueContainer);
+    return value == null ? false : ((Boolean) value).booleanValue();
+  }
+
+  /**
+   * Sets the boolean value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param value          the boolean value
+   */
+  public void set(Object valueContainer, boolean value) {
+    setValue(valueContainer, Boolean.valueOf(value));
+  }
+
+}
diff --git a/src/net/infonode/properties/types/BorderProperty.java b/src/net/infonode/properties/types/BorderProperty.java
new file mode 100644
index 0000000..380d936
--- /dev/null
+++ b/src/net/infonode/properties/types/BorderProperty.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BorderProperty.java,v 1.7 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+import javax.swing.border.Border;
+
+/**
+ * A property of type {@link Border}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class BorderProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public BorderProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueHandler) {
+    super(group, name, Border.class, description, valueHandler);
+  }
+
+  /**
+   * Returns the border value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the border value of this property
+   */
+  public Border get(Object valueContainer) {
+    return (Border) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the border value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param border         the border value
+   */
+  public void set(Object valueContainer, Border border) {
+    setValue(valueContainer, border);
+  }
+}
diff --git a/src/net/infonode/properties/types/ButtonFactoryProperty.java b/src/net/infonode/properties/types/ButtonFactoryProperty.java
new file mode 100644
index 0000000..6a72545
--- /dev/null
+++ b/src/net/infonode/properties/types/ButtonFactoryProperty.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ButtonFactoryProperty.java,v 1.2 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.gui.button.ButtonFactory;
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A property which has {@link ButtonFactory}'s as values.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ */
+public class ButtonFactoryProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public ButtonFactoryProperty(PropertyGroup group,
+                               String name,
+                               String description,
+                               PropertyValueHandler valueHandler) {
+    super(group, name, ButtonFactory.class, description, valueHandler);
+  }
+
+  /**
+   * Returns the {@link ButtonFactory} value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the button factory value of this property
+   */
+  public ButtonFactory get(Object valueContainer) {
+    return (ButtonFactory) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the {@link ButtonFactory} value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param buttonFactory  the button factory
+   */
+  public void set(Object valueContainer, ButtonFactory buttonFactory) {
+    setValue(valueContainer, buttonFactory);
+  }
+}
diff --git a/src/net/infonode/properties/types/ColorProperty.java b/src/net/infonode/properties/types/ColorProperty.java
new file mode 100644
index 0000000..8191f87
--- /dev/null
+++ b/src/net/infonode/properties/types/ColorProperty.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ColorProperty.java,v 1.7 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+import java.awt.*;
+
+/**
+ * A property of type {@link Color}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class ColorProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public ColorProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueHandler) {
+    super(group, name, Color.class, description, valueHandler);
+  }
+
+  /**
+   * Returns the color value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the color value of this property
+   */
+  public Color get(Object valueContainer) {
+    return (Color) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the color value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param color          the color value
+   */
+  public void set(Object valueContainer, Color color) {
+    setValue(valueContainer, color);
+  }
+
+}
diff --git a/src/net/infonode/properties/types/ComponentPainterProperty.java b/src/net/infonode/properties/types/ComponentPainterProperty.java
new file mode 100644
index 0000000..f8a1360
--- /dev/null
+++ b/src/net/infonode/properties/types/ComponentPainterProperty.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ComponentPainterProperty.java,v 1.4 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.gui.componentpainter.ComponentPainter;
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A property of type {@link ComponentPainter}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class ComponentPainterProperty extends ValueHandlerProperty {
+  public ComponentPainterProperty(PropertyGroup group,
+                                  String name,
+                                  String description,
+                                  PropertyValueHandler valueStorage) {
+    super(group, name, ComponentPainter.class, description, valueStorage);
+  }
+
+  /**
+   * Returns the {@link ComponentPainter} value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the {@link ComponentPainter} value of this property
+   */
+  public ComponentPainter get(Object valueContainer) {
+    return (ComponentPainter) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the {@link ComponentPainter} value of this property in a value container.
+   *
+   * @param valueContainer   the value container
+   * @param componentPainter the {@link ComponentPainter} value
+   */
+  public void set(Object valueContainer, ComponentPainter componentPainter) {
+    setValue(valueContainer, componentPainter);
+  }
+
+}
diff --git a/src/net/infonode/properties/types/DimensionProviderProperty.java b/src/net/infonode/properties/types/DimensionProviderProperty.java
new file mode 100644
index 0000000..0b38a90
--- /dev/null
+++ b/src/net/infonode/properties/types/DimensionProviderProperty.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+package net.infonode.properties.types;
+
+import net.infonode.gui.DimensionProvider;
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A property of type {@link net.infonode.gui.DimensionProvider}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ */
+public class DimensionProviderProperty extends ValueHandlerProperty {
+  public DimensionProviderProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueStorage) {
+    super(group, name, DimensionProvider.class, description, valueStorage);
+  }
+
+  /**
+   * Returns the dimension provider value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the dimension provider value of this property
+   */
+  public DimensionProvider get(Object valueContainer) {
+    return (DimensionProvider) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the dimension provider value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param dimension      the dimension provider value
+   */
+  public void set(Object valueContainer, DimensionProvider dimension) {
+    setValue(valueContainer, dimension);
+  }
+}
diff --git a/src/net/infonode/properties/types/DirectionProperty.java b/src/net/infonode/properties/types/DirectionProperty.java
new file mode 100644
index 0000000..36410ee
--- /dev/null
+++ b/src/net/infonode/properties/types/DirectionProperty.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DirectionProperty.java,v 1.6 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.util.Direction;
+
+/**
+ * A property of type {@link Direction}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class DirectionProperty extends EnumProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public DirectionProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueHandler) {
+    super(group, name, Direction.class, description, valueHandler, Direction.getDirections());
+  }
+
+  /**
+   * Returns the {@link Direction} value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the boolean value of this property
+   */
+  public Direction get(Object valueContainer) {
+    return (Direction) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the {@link Direction} value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param direction      the {@link Direction} value
+   */
+  public void set(Object valueContainer, Direction direction) {
+    setValue(valueContainer, direction);
+  }
+}
diff --git a/src/net/infonode/properties/types/EnumProperty.java b/src/net/infonode/properties/types/EnumProperty.java
new file mode 100644
index 0000000..3ae0aca
--- /dev/null
+++ b/src/net/infonode/properties/types/EnumProperty.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: EnumProperty.java,v 1.7 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+import net.infonode.util.ArrayUtil;
+
+/**
+ * A property which value is one in a fixed set of values.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class EnumProperty extends ValueHandlerProperty {
+  private Object[] validValues;
+
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param type         the property type
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   * @param validValues  valid values for this property
+   */
+  public EnumProperty(PropertyGroup group,
+                      String name,
+                      Class type,
+                      String description,
+                      PropertyValueHandler valueHandler,
+                      Object[] validValues) {
+    super(group, name, type, description, valueHandler);
+    this.validValues = (Object[]) validValues.clone();
+  }
+
+  public void setValue(Object object, Object value) {
+    if (!ArrayUtil.contains(validValues, value))
+      throw new IllegalArgumentException("Invalid enum value!");
+
+    super.setValue(object, value);
+  }
+
+  /**
+   * Returns the valid values for this property.
+   *
+   * @return the valid values for this property
+   */
+  public Object[] getValidValues() {
+    return (Object[]) validValues.clone();
+  }
+
+  public Object getValue(Object object) {
+    Object value = super.getValue(object);
+    return value == null ? validValues[0] : value;
+  }
+}
diff --git a/src/net/infonode/properties/types/FloatProperty.java b/src/net/infonode/properties/types/FloatProperty.java
new file mode 100644
index 0000000..9d0ab47
--- /dev/null
+++ b/src/net/infonode/properties/types/FloatProperty.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FloatProperty.java,v 1.5 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A float property.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class FloatProperty extends ValueHandlerProperty {
+  private float minValue;
+  private float maxValue;
+  private int preferredDigitCount;
+  private float preferredDelta;
+
+  /**
+   * Constructor.
+   * Creates an unbounded float property.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public FloatProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueHandler) {
+    this(group, name, description, valueHandler, Float.MIN_VALUE, Float.MAX_VALUE);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   * @param minValue     the smallest value that this property can have
+   * @param maxValue     the largest value that this property can have
+   */
+  public FloatProperty(PropertyGroup group,
+                       String name,
+                       String description,
+                       PropertyValueHandler valueHandler,
+                       float minValue,
+                       float maxValue) {
+    this(group, name, description, valueHandler, minValue, maxValue, 6, 0.1f);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param group               the property group
+   * @param name                the property name
+   * @param description         the property description
+   * @param valueHandler        handles values for this property
+   * @param minValue            the smallest value that this property can have
+   * @param maxValue            the largest value that this property can have
+   * @param preferredDigitCount the preferred number of digits to allocate space for in an editor for a property value
+   * @param preferredDelta      the preferred amount to increase and decrease a property value by
+   */
+  public FloatProperty(PropertyGroup group,
+                       String name,
+                       String description,
+                       PropertyValueHandler valueHandler,
+                       float minValue,
+                       float maxValue,
+                       int preferredDigitCount,
+                       float preferredDelta) {
+    super(group, name, Float.class, description, valueHandler);
+    this.minValue = minValue;
+    this.maxValue = maxValue;
+    this.preferredDigitCount = preferredDigitCount;
+    this.preferredDelta = preferredDelta;
+  }
+
+  /**
+   * Returns the preferred amount to increase and decrease a property value by.
+   *
+   * @return the preferred amount to increase and decrease a property value by
+   */
+  public float getPreferredDelta() {
+    return preferredDelta;
+  }
+
+  /**
+   * Returns the smallest value that this property can have.
+   *
+   * @return the smallest value that this property can have
+   */
+  public float getMinValue() {
+    return minValue;
+  }
+
+  /**
+   * Returns the largest value that this property can have.
+   *
+   * @return the largest value that this property can have
+   */
+  public float getMaxValue() {
+    return maxValue;
+  }
+
+  /**
+   * Returns the preferred number of digits to allocate space for in an editor for a property value.
+   *
+   * @return the preferred number of digits to allocate space for in an editor for a property value
+   */
+  public int getPreferredDigitCount() {
+    return preferredDigitCount;
+  }
+
+  /**
+   * Returns the float value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the float value of this property
+   */
+  public float get(Object valueContainer) {
+    Object value = getValue(valueContainer);
+    return value == null ? 0 : ((Number) getValue(valueContainer)).floatValue();
+  }
+
+  /**
+   * Sets the float value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param value          the float value
+   */
+  public void set(Object valueContainer, float value) {
+    setValue(valueContainer, new Float(value));
+  }
+
+  public boolean canBeAssiged(Object value) {
+    if (!super.canBeAssiged(value))
+      return false;
+
+    float v = ((Number) value).floatValue();
+    return v >= minValue && v <= maxValue;
+  }
+}
diff --git a/src/net/infonode/properties/types/FontProperty.java b/src/net/infonode/properties/types/FontProperty.java
new file mode 100644
index 0000000..14a5342
--- /dev/null
+++ b/src/net/infonode/properties/types/FontProperty.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: FontProperty.java,v 1.7 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+import java.awt.*;
+
+/**
+ * A property of type {@link Font}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class FontProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public FontProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueHandler) {
+    super(group, name, Font.class, description, valueHandler);
+  }
+
+  /**
+   * Returns the font value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the font value of this property
+   */
+  public Font get(Object valueContainer) {
+    return (Font) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the font value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param font           the font value
+   */
+  public void set(Object valueContainer, Font font) {
+    setValue(valueContainer, font);
+  }
+
+}
diff --git a/src/net/infonode/properties/types/HoverListenerProperty.java b/src/net/infonode/properties/types/HoverListenerProperty.java
new file mode 100644
index 0000000..7618ab4
--- /dev/null
+++ b/src/net/infonode/properties/types/HoverListenerProperty.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: HoverListenerProperty.java,v 1.4 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A property of type {@link HoverListener}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class HoverListenerProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public HoverListenerProperty(PropertyGroup group,
+                               String name,
+                               String description,
+                               PropertyValueHandler valueHandler) {
+    super(group, name, HoverListener.class, description, valueHandler);
+  }
+
+  /**
+   * Returns the hover listener value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the hover listener value of this property
+   */
+  public HoverListener get(Object valueContainer) {
+    return (HoverListener) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the hover listener value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param listener       the hover listener value
+   */
+  public void set(Object valueContainer, HoverListener listener) {
+    setValue(valueContainer, listener);
+  }
+}
diff --git a/src/net/infonode/properties/types/IconProperty.java b/src/net/infonode/properties/types/IconProperty.java
new file mode 100644
index 0000000..97783eb
--- /dev/null
+++ b/src/net/infonode/properties/types/IconProperty.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: IconProperty.java,v 1.7 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+import javax.swing.*;
+
+/**
+ * A property of type {@link Icon}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class IconProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public IconProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueHandler) {
+    super(group, name, Icon.class, description, valueHandler);
+  }
+
+  /**
+   * Sets the icon value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param icon           the icon value
+   */
+  public void set(Object valueContainer, Icon icon) {
+    setValue(valueContainer, icon);
+  }
+
+  /**
+   * Returns the icon value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the icon value of this property
+   */
+  public Icon get(Object valueContainer) {
+    return (Icon) getValue(valueContainer);
+  }
+
+}
diff --git a/src/net/infonode/properties/types/InsetsProperty.java b/src/net/infonode/properties/types/InsetsProperty.java
new file mode 100644
index 0000000..fb2f3ea
--- /dev/null
+++ b/src/net/infonode/properties/types/InsetsProperty.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: InsetsProperty.java,v 1.7 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+import java.awt.*;
+
+/**
+ * A property of type {@link Insets}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class InsetsProperty extends ValueHandlerProperty {
+  public InsetsProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueStorage) {
+    super(group, name, Insets.class, description, valueStorage);
+  }
+
+  /**
+   * Returns the insets value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the insets value of this property
+   */
+  public Insets get(Object valueContainer) {
+    return (Insets) getValue(valueContainer);
+  }
+
+  /**
+   * Sets the insets value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param insets         the insets value
+   */
+  public void set(Object valueContainer, Insets insets) {
+    setValue(valueContainer, insets);
+  }
+
+}
diff --git a/src/net/infonode/properties/types/IntegerProperty.java b/src/net/infonode/properties/types/IntegerProperty.java
new file mode 100644
index 0000000..2b83223
--- /dev/null
+++ b/src/net/infonode/properties/types/IntegerProperty.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: IntegerProperty.java,v 1.4 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+
+/**
+ * An integer property.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class IntegerProperty extends NumberProperty {
+  /**
+   * Constructor.
+   * Creates an unbounded integer property.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public IntegerProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueHandler) {
+    this(group, name, description, Integer.MIN_VALUE, Integer.MAX_VALUE, -1, valueHandler);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param group               the property group
+   * @param name                the property name
+   * @param description         the property description
+   * @param min                 the smallest value that this property can have
+   * @param max                 the largest value that this property can have
+   * @param preferredDigitCount the preferred number of digits to allocate space for in an editor for a property value
+   * @param valueHandler        handles values for this property
+   */
+  public IntegerProperty(PropertyGroup group, String name, String description, int min, int max, int preferredDigitCount,
+                         PropertyValueHandler valueHandler) {
+    super(group, name, Integer.class, description, min, max, preferredDigitCount, valueHandler);
+  }
+
+  /**
+   * Creates an integer property that can only be set to zero and positive integers.
+   *
+   * @param group               the property group
+   * @param name                the property name
+   * @param description         the property description
+   * @param preferredDigitCount the preferred number of digits to allocate space for in an editor for a property value
+   * @param valueHandler        handles values for this property
+   * @return an an integer property that can only be set to zero and positive integers
+   */
+  public static IntegerProperty createPositive(PropertyGroup group, String name, String description, int preferredDigitCount,
+                                               PropertyValueHandler valueHandler) {
+    return new IntegerProperty(group, name, description, 0, Integer.MAX_VALUE, preferredDigitCount, valueHandler);
+  }
+
+  /**
+   * Returns the integer value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the integer value of this property
+   */
+  public int get(Object valueContainer) {
+    return (int) getLongValue(valueContainer);
+  }
+
+  /**
+   * Sets the integer value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param value          the float value
+   */
+  public void set(Object valueContainer, int value) {
+    setValue(valueContainer, new Integer(value));
+  }
+
+}
diff --git a/src/net/infonode/properties/types/NumberProperty.java b/src/net/infonode/properties/types/NumberProperty.java
new file mode 100644
index 0000000..9cb16c8
--- /dev/null
+++ b/src/net/infonode/properties/types/NumberProperty.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: NumberProperty.java,v 1.4 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * Base class for number properties.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class NumberProperty extends ValueHandlerProperty {
+  private long minValue;
+  private long maxValue;
+  private int preferredDigitCount;
+
+  /**
+   * Constructor.
+   *
+   * @param group               the property group
+   * @param name                the property name
+   * @param cl                  the property type
+   * @param description         the property description
+   * @param minValue            the smallest value that this property can have
+   * @param maxValue            the largest value that this property can have
+   * @param preferredDigitCount the preferred number of digits to allocate space for in an editor for a property value
+   * @param valueHandler        handles values for this property
+   */
+  public NumberProperty(PropertyGroup group, String name, Class cl, String description, long minValue, long maxValue,
+                        int preferredDigitCount, PropertyValueHandler valueHandler) {
+    super(group, name, cl, description, valueHandler);
+    this.minValue = minValue;
+    this.maxValue = maxValue;
+    this.preferredDigitCount = preferredDigitCount;
+  }
+
+  /**
+   * Returns the preferred number of digits to allocate space for in an editor for a property value
+   *
+   * @return the preferred number of digits to allocate space for in an editor for a property value
+   */
+  public int getPreferredDigitCount() {
+    return preferredDigitCount;
+  }
+
+  /**
+   * Returns the smallest value that this property can have.
+   *
+   * @return the smallest value that this property can have
+   */
+  public long getMinValue() {
+    return minValue;
+  }
+
+  /**
+   * Returns the largest value that this property can have.
+   *
+   * @return the largest value that this property can have
+   */
+  public long getMaxValue() {
+    return maxValue;
+  }
+
+  /**
+   * Returns the long value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the long value of this property
+   */
+  public long getLongValue(Object valueContainer) {
+    Object value = getValue(valueContainer);
+    return value == null ? Math.max(0, minValue) : ((Number) getValue(valueContainer)).longValue();
+  }
+
+  public boolean canBeAssiged(Object value) {
+    if (!super.canBeAssiged(value))
+      return false;
+
+    long v = ((Number) value).longValue();
+    return minValue <= v && maxValue >= v;
+  }
+
+}
diff --git a/src/net/infonode/properties/types/PropertyGroupProperty.java b/src/net/infonode/properties/types/PropertyGroupProperty.java
new file mode 100644
index 0000000..40e3883
--- /dev/null
+++ b/src/net/infonode/properties/types/PropertyGroupProperty.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyGroupProperty.java,v 1.5 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A property that can be assigned a value container as value.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public class PropertyGroupProperty extends ValueHandlerProperty {
+  private PropertyGroup propertyGroup;
+
+  /**
+   * Constructor.
+   *
+   * @param group         the property group
+   * @param name          the property name
+   * @param type          the property type
+   * @param description   the property description
+   * @param valueHandler  handles values for this property
+   * @param propertyGroup the property group. Values for properties in this group can be stored in the value containers
+   *                      that are this properties values.
+   */
+  public PropertyGroupProperty(PropertyGroup group, String name, Class type, String description, PropertyValueHandler valueHandler,
+                               PropertyGroup propertyGroup) {
+    super(group, name, type, description, valueHandler);
+    this.propertyGroup = propertyGroup;
+  }
+
+  /**
+   * Returns the property group.
+   * Values for properties in this group can be stored in the value containers that are this properties values.
+   *
+   * @return the property group
+   */
+  public PropertyGroup getPropertyGroup() {
+    return propertyGroup;
+  }
+}
diff --git a/src/net/infonode/properties/types/StringProperty.java b/src/net/infonode/properties/types/StringProperty.java
new file mode 100644
index 0000000..4065682
--- /dev/null
+++ b/src/net/infonode/properties/types/StringProperty.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: StringProperty.java,v 1.4 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.types;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.util.PropertyValueHandler;
+import net.infonode.properties.util.ValueHandlerProperty;
+
+/**
+ * A {@link String} property.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public class StringProperty extends ValueHandlerProperty {
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public StringProperty(PropertyGroup group, String name, String description, PropertyValueHandler valueHandler) {
+    super(group, name, String.class, description, valueHandler);
+  }
+
+  /**
+   * Returns the string value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @return the string value of this property
+   */
+  public String get(Object valueContainer) {
+    Object value = getValue(valueContainer);
+    return value == null ? null : value.toString();
+  }
+
+  /**
+   * Sets the string value of this property in a value container.
+   *
+   * @param valueContainer the value container
+   * @param value          the string value
+   */
+  public void set(Object valueContainer, String value) {
+    setValue(valueContainer, value);
+  }
+}
diff --git a/src/net/infonode/properties/types/package.html b/src/net/infonode/properties/types/package.html
new file mode 100644
index 0000000..477574f
--- /dev/null
+++ b/src/net/infonode/properties/types/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Classes for property types.
+</body>
+</html>
diff --git a/src/net/infonode/properties/util/AbstractProperty.java b/src/net/infonode/properties/util/AbstractProperty.java
new file mode 100644
index 0000000..a71df95
--- /dev/null
+++ b/src/net/infonode/properties/util/AbstractProperty.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractProperty.java,v 1.4 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.util;
+
+import net.infonode.properties.base.Property;
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.base.exception.InvalidPropertyValueException;
+
+/**
+ * An abstract base class for properties.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+abstract public class AbstractProperty implements Property {
+  private PropertyGroup group;
+  private String name;
+  private Class type;
+  private String description;
+
+  /**
+   * Constructor.
+   *
+   * @param group       the property group
+   * @param name        the property name
+   * @param type        the property type
+   * @param description the property description
+   */
+  protected AbstractProperty(PropertyGroup group, String name, Class type, String description) {
+    this.group = group;
+    this.name = name;
+    this.type = type;
+    this.description = description;
+
+    if (group != null)
+      group.addProperty(this);
+  }
+
+  public PropertyGroup getGroup() {
+    return group;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Class getType() {
+    return type;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public boolean isMutable() {
+    return true;
+  }
+
+  public void setValue(Object object, Object value) {
+    if (!canBeAssiged(value))
+      throw new InvalidPropertyValueException(this, value);
+  }
+
+  public String toString() {
+    return getName();
+  }
+
+  public boolean canBeAssiged(Object value) {
+    return isMutable() && (value == null || getType().isAssignableFrom(value.getClass()));
+  }
+
+}
diff --git a/src/net/infonode/properties/util/PropertyChangeListener.java b/src/net/infonode/properties/util/PropertyChangeListener.java
new file mode 100644
index 0000000..9f751fe
--- /dev/null
+++ b/src/net/infonode/properties/util/PropertyChangeListener.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyChangeListener.java,v 1.3 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.util;
+
+import net.infonode.properties.base.Property;
+
+/**
+ * The listener interface for receiving changes to a property value.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public interface PropertyChangeListener {
+  /**
+   * Invoked when a property value has changed.
+   *
+   * @param property       the property
+   * @param valueContainer the object containing the value
+   * @param oldValue       the old property value
+   * @param newValue       the new property value
+   */
+  void propertyChanged(Property property, Object valueContainer, Object oldValue, Object newValue);
+}
diff --git a/src/net/infonode/properties/util/PropertyPath.java b/src/net/infonode/properties/util/PropertyPath.java
new file mode 100644
index 0000000..63be722
--- /dev/null
+++ b/src/net/infonode/properties/util/PropertyPath.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyPath.java,v 1.3 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.util;
+
+import net.infonode.properties.base.Property;
+
+/**
+ * A path to a property.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class PropertyPath {
+  private Property property;
+  private PropertyPath tail;
+
+  /**
+   * Creates a path containing a single property.
+   *
+   * @param property the property
+   */
+  public PropertyPath(Property property) {
+    this(property, null);
+  }
+
+  /**
+   * Creates a path by prepending a path with a property.
+   *
+   * @param property the property to prepend
+   * @param tail     the path to prepend to
+   */
+  public PropertyPath(Property property, PropertyPath tail) {
+    this.property = property;
+    this.tail = tail;
+  }
+
+  /**
+   * Returns the first property in this path.
+   *
+   * @return the first property in the path
+   */
+  public Property getProperty() {
+    return property;
+  }
+
+  /**
+   * Returns the path after the first property.
+   *
+   * @return the path after the first property
+   */
+  public PropertyPath getTail() {
+    return tail;
+  }
+
+  /**
+   * Creates a new path that is a copy of this path.
+   * The properties are not copied.
+   *
+   * @return a copy of this path
+   */
+  public PropertyPath copy() {
+    return new PropertyPath(property, getTail() == null ? null : getTail().copy());
+  }
+
+}
diff --git a/src/net/infonode/properties/util/PropertyValueHandler.java b/src/net/infonode/properties/util/PropertyValueHandler.java
new file mode 100644
index 0000000..ce207e4
--- /dev/null
+++ b/src/net/infonode/properties/util/PropertyValueHandler.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PropertyValueHandler.java,v 1.4 2004/09/22 14:32:50 jesper Exp $
+package net.infonode.properties.util;
+
+import net.infonode.properties.base.Property;
+
+/**
+ * Sets and gets property values to and from value objects.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public interface PropertyValueHandler {
+  /**
+   * Gets the value of a property from a value container.
+   *
+   * @param property       the property
+   * @param valueContainer the object containing the value
+   * @return the property value, null if the container doesn't contain the value
+   */
+  Object getValue(Property property, Object valueContainer);
+
+  /**
+   * Sets the value of a property in a value container.
+   *
+   * @param property       the property
+   * @param valueContainer the object that will contain the value
+   * @param value          the property value
+   */
+  void setValue(Property property, Object valueContainer, Object value);
+
+  /**
+   * Removes a property value from a value container.
+   *
+   * @param property       the property
+   * @param valueContainer the value container
+   */
+  void removeValue(Property property, Object valueContainer);
+
+  /**
+   * Returns true if a value for the property is set in the value container.
+   *
+   * @param property       the property
+   * @param valueContainer the value container
+   * @return true if a value for the property is set in the value container
+   */
+  boolean getValueIsSet(Property property, Object valueContainer);
+
+  /**
+   * Returns true if the property value is removable from the value container.
+   *
+   * @param property       the property
+   * @param valueContainer the value container
+   * @return true if the property value is removable from the value container
+   */
+  boolean getValueIsRemovable(Property property, Object valueContainer);
+}
diff --git a/src/net/infonode/properties/util/ValueHandlerProperty.java b/src/net/infonode/properties/util/ValueHandlerProperty.java
new file mode 100644
index 0000000..c67ff05
--- /dev/null
+++ b/src/net/infonode/properties/util/ValueHandlerProperty.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ValueHandlerProperty.java,v 1.6 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.properties.util;
+
+import net.infonode.properties.base.PropertyGroup;
+
+/**
+ * Base class for properties that use a {@link PropertyValueHandler}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public class ValueHandlerProperty extends AbstractProperty {
+  private PropertyValueHandler valueHandler;
+
+  /**
+   * Constructor.
+   *
+   * @param group        the property group
+   * @param name         the property name
+   * @param type         the property type
+   * @param description  the property description
+   * @param valueHandler handles values for this property
+   */
+  public ValueHandlerProperty(PropertyGroup group,
+                              String name,
+                              Class type,
+                              String description,
+                              PropertyValueHandler valueHandler) {
+    super(group, name, type, description);
+    this.valueHandler = valueHandler;
+  }
+
+  public void setValue(Object object, Object value) {
+    super.setValue(object, value);
+    valueHandler.setValue(this, object, value);
+  }
+
+  public Object getValue(Object object) {
+    return valueHandler.getValue(this, object);
+  }
+
+  public boolean valueIsRemovable(Object object) {
+    return valueHandler.getValueIsRemovable(this, object);
+  }
+
+  public void removeValue(Object object) {
+    valueHandler.removeValue(this, object);
+  }
+
+  public boolean valueIsSet(Object object) {
+    return valueHandler.getValueIsSet(this, object);
+  }
+}
diff --git a/src/net/infonode/properties/util/package.html b/src/net/infonode/properties/util/package.html
new file mode 100644
index 0000000..5a7e066
--- /dev/null
+++ b/src/net/infonode/properties/util/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Utility classes for properties.
+</body>
+</html>
diff --git a/src/net/infonode/tabbedpanel/Tab.java b/src/net/infonode/tabbedpanel/Tab.java
new file mode 100644
index 0000000..c7854b1
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/Tab.java
@@ -0,0 +1,548 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Tab.java,v 1.33 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.gui.draggable.DraggableComponent;
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.ArrayList;
+
+/**
+ * <p>A Tab is a component that represents a tab in a {@link TabbedPanel}.</p>
+ *
+ * <p>A tab can hold a content component. The content component will then be shown in
+ * the content area of the TabbedPanel that the tab is a member of when the tab is
+ * selected. If the tab doesn't have a content component, then the TabbedPanel will not
+ * show any content in the content area, i.e. it will be empty.</p>
+ *
+ * <p>The tab is basically a JPanel with a BorderLayout. The layout manager can be
+ * changed using setLayout. Components and borders can be added and removed from the
+ * tab. The tab can also be subclassed to create other types of tabs, see
+ * {@link TitledTab}. <strong>In most cases {@link TitledTab} is the preferred tab type
+ * to use because TitledTab adds support for a text, icon, looks etc.</strong></p>
+ *
+ * <p>The tab component will be shown in the tab area of a TabbedPanel
+ * after the tab has become a member of that TabbedPanel by either adding or inserting
+ * it. A tab can only be a member of one TabbedPanel at the same time.</p>
+ *
+ * <p>A tab can have different states when it is a member of a TabbedPanel:
+ * <ul>
+ * <li>Normal: This means that the tab is shown (and not selected) in the TabbedPanel. The
+ * content component is not shown until the user selects the tab.
+ * <li>Highlighted: This means that for some reason the tab should be highlighted in
+ * the TabbedPanel. Highlighted could mean that the user pressed the tab with the mouse
+ * and has not yet released the mouse, i.e. it has not been selected yet.
+ * <li>Selected: This means that the tab is selected in the TabbedPanel. The TabbedPanel
+ * will then show the Tab's content component (if any). A selected tab will also be
+ * be highlighted before it is selected.
+ * <li>Enabled: This means that the tab is enabled and can be selected, highlighted
+ * dragged, moved etc.
+ * <li>Disabled: This means that the tab cannot be selected, highlighted
+ * dragged, moved etc.
+ * </ul></p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.33 $
+ * @see TabListener
+ * @see TabbedPanel
+ * @see TitledTab
+ */
+public class Tab extends JPanel {
+  private TabbedPanel tabbedPanel;
+  private JComponent contentComponent;
+  private JComponent focusableComponent;
+  private ArrayList listeners;
+  private DraggableComponent draggableComponent;
+
+  private KeyListener focusableKeyListener = new KeyAdapter() {
+    public void keyPressed(KeyEvent e) {
+      if (tabbedPanel != null) {
+        Direction tabOrientation = tabbedPanel.getProperties().getTabAreaOrientation();
+        int incKey = tabOrientation.isHorizontal() ? KeyEvent.VK_DOWN : KeyEvent.VK_RIGHT;
+        int decKey = tabOrientation.isHorizontal() ? KeyEvent.VK_UP : KeyEvent.VK_LEFT;
+        int index = tabbedPanel.getTabIndex(Tab.this);
+        while (true) {
+          index = (index + tabbedPanel.getTabCount() + (e.getKeyCode() == incKey ?
+                                                        1 : e.getKeyCode() == decKey ? -1 : 0)) %
+                  tabbedPanel.getTabCount();
+          Tab tab = tabbedPanel.getTabAt(index);
+          if (tab == Tab.this)
+            return;
+
+          if (tab.getFocusableComponent() != null) {
+            /*tab.getFocusableComponent().setFocusable(true);
+            tab.getFocusableComponent().requestFocusInWindow();*/
+            tab.setSelected(true);
+            break;
+          }
+        }
+      }
+    }
+  };
+
+  private TabListener tabbedPanelListener = new TabListener() {
+    public void tabAdded(TabEvent event) {
+      if (event.getTab() == Tab.this)
+        fireAddedEvent();
+    }
+
+    public void tabRemoved(TabRemovedEvent event) {
+      if (event.getTab() == Tab.this) {
+        event.getTabbedPanel().removeTabListener(this);
+        fireRemovedEvent(event);
+      }
+    }
+
+    public void tabMoved(TabEvent event) {
+      if (event.getTab() == Tab.this)
+        fireMovedEvent();
+    }
+
+    public void tabDragged(TabDragEvent event) {
+      if (event.getTab() == Tab.this)
+        fireDraggedEvent(event);
+    }
+
+    public void tabDropped(TabDragEvent event) {
+      if (event.getTab() == Tab.this)
+        fireDroppedEvent(event);
+    }
+
+    public void tabDragAborted(TabEvent event) {
+      if (event.getTab() == Tab.this)
+        fireNotDroppedEvent();
+    }
+
+    public void tabSelected(TabStateChangedEvent event) {
+      if (event.getTab() == Tab.this) {
+        Tab tab = event.getPreviousTab();
+        boolean hasFocus = tab != null && tab.getFocusableComponent() != null && tab.getFocusableComponent().hasFocus();
+
+        if (tab != null && tab.getFocusableComponent() != null)
+          tab.getFocusableComponent().setFocusable(false);
+
+        if (focusableComponent != null) {
+          focusableComponent.setFocusable(true);
+
+          if (hasFocus)
+            focusableComponent.requestFocusInWindow();
+        }
+
+        fireSelectedEvent(event);
+      }
+    }
+
+    public void tabDeselected(TabStateChangedEvent event) {
+      if (event.getTab() == Tab.this) {
+        fireDeselectedEvent(event);
+      }
+    }
+
+    public void tabHighlighted(TabStateChangedEvent event) {
+      if (event.getTab() == Tab.this)
+        fireHighlightedEvent(event);
+    }
+
+    public void tabDehighlighted(TabStateChangedEvent event) {
+      if (event.getPreviousTab() == Tab.this)
+        fireDehighlightedEvent(event);
+    }
+  };
+
+  /**
+   * Constructs a tab without a content component and this tab as event
+   * component
+   *
+   * @see #setEventComponent
+   */
+  public Tab() {
+    this(null);
+  }
+
+  /**
+   * Constructs a tab with a content component and this tab as event
+   * component
+   *
+   * @param contentComponent content component for this tab or null for
+   *                         no content component.
+   * @see #setEventComponent
+   */
+  public Tab(JComponent contentComponent) {
+    super(new BorderLayout());
+    setOpaque(false);
+    this.contentComponent = contentComponent;
+    draggableComponent = new DraggableComponent(this);
+  }
+
+  /**
+   * Adds a TabListener
+   *
+   * @param listener the TabListener to add
+   */
+  public void addTabListener(TabListener listener) {
+    if (listeners == null)
+      listeners = new ArrayList(2);
+
+    listeners.add(listener);
+  }
+
+  /**
+   * Removes a TabListener
+   *
+   * @param listener the TabListener to remove
+   */
+  public void removeTabListener(TabListener listener) {
+    if (listeners != null) {
+      listeners.remove(listener);
+
+      if (listeners.size() == 0)
+        listeners = null;
+    }
+  }
+
+  /**
+   * Gets the content component
+   *
+   * @return the content component for this tab or null if this Tab
+   *         doesn't have a content component
+   */
+  public JComponent getContentComponent() {
+    return contentComponent;
+  }
+
+  /**
+   * Gets the TabbedPanel that this tab is a member of
+   *
+   * @return the TabbedPanel or null if this tab is not a member of
+   *         any TabbedPanel
+   */
+  public TabbedPanel getTabbedPanel() {
+    return tabbedPanel;
+  }
+
+  /**
+   * <p>Enable or disable this tab.</p>
+   *
+   * <p>If the tab is disabled, then the tab will not signal any events
+   * until it is enabled again.</p>
+   *
+   * @param enabled true for enabled, otherwise false
+   */
+  public void setEnabled(boolean enabled) {
+    getDraggableComponent().setEnabled(enabled);
+    super.setEnabled(enabled);
+  }
+
+  /**
+   * <p>Selects this tab. A tab can only have the selected state if it is a
+   * member of a TabbedPanel.</p>
+   *
+   * <p>Setting selected to true means that this tab will be the selected
+   * tab in the TabbedPanel it is a member of. If this tab is the selected
+   * tab in the TabbedPanel then setting selected to false means there will
+   * be no selected tab in the TabbedPanel until another tab is selected.</p>
+   *
+   * @param selected True for selected, otherwise false
+   */
+  public void setSelected(boolean selected) {
+    if (selected)
+      draggableComponent.select();
+    else if (tabbedPanel != null && tabbedPanel.getSelectedTab() == this)
+      tabbedPanel.setSelectedTab(null);
+  }
+
+  /**
+   * Returns if this tab is selected in the TabbedPanel that it is a member of.
+   *
+   * @return true if selected, false if not selected or this tab is not member
+   *         of a TabbedPanel
+   */
+  public boolean isSelected() {
+    return tabbedPanel != null ? tabbedPanel.getSelectedTab() == this : false;
+  }
+
+  /**
+   * Highlights this tab. This tab will be the highlighted tab in the TabbedPanel
+   * that it is member of.
+   *
+   * @param highlighted true for highlight, otherwise false
+   */
+  public void setHighlighted(boolean highlighted) {
+    if (tabbedPanel != null) {
+      if (highlighted)
+        tabbedPanel.setHighlightedTab(this);
+      else if (tabbedPanel.getHighlightedTab() == this)
+        tabbedPanel.setHighlightedTab(null);
+    }
+  }
+
+  /**
+   * Returns if this tab is highlighted in the TabbedPanel that it is a member of.
+   *
+   * @return true if highlighted, false if not highlighted or this tab is not member
+   *         of a TabbedPanel
+   */
+  public boolean isHighlighted() {
+    return tabbedPanel != null ? tabbedPanel.getHighlightedTab() == this : false;
+  }
+
+  /**
+   * <p>Sets the event component. An event component is a component in the tab that
+   * is used for internal listening to mouse events on the tab.</p>
+   *
+   * <p><strong>Note:</strong> The event component must be part of this Tab</p>
+   *
+   * @param eventComponent a component in this tab that should be used for mouse
+   *                       event listening
+   */
+  public void setEventComponent(JComponent eventComponent) {
+    setEventComponents(new JComponent[]{eventComponent});
+  }
+
+  /**
+   * <p>Sets a list of event components. An event component is a component in the
+   * tab that is used for internal listening to mouse events on the tab. This
+   * method makes it possible to use several components in the tab as event
+   * components.</p>
+   *
+   * <p><strong>Note:</strong> The event components must be part of this Tab</p>
+   *
+   * @param eventComponents a list of components in this tab that should be used for
+   *                        mouse event listening
+   */
+  public void setEventComponents(JComponent[] eventComponents) {
+    draggableComponent.setEventComponents(eventComponents);
+  }
+
+  /**
+   * Gets the event components for this Tab
+   *
+   * @return a list of all event components for this tab
+   */
+  public JComponent[] getEventComponents() {
+    return draggableComponent.getEventComponents();
+  }
+
+  /**
+   * Gets the index of this tab in the TabbedPanel.
+   *
+   * @return the tab index, -1 if this tab is not a member of a TabbedPanel.
+   */
+  public int getIndex() {
+    return tabbedPanel == null ? -1 : tabbedPanel.getTabIndex(this);
+  }
+
+  /**
+   * Gets the component in this tab that is focusable
+   *
+   * @return focusable component or null if this tab doesn't have any focusable
+   *         component
+   */
+  public JComponent getFocusableComponent() {
+    return focusableComponent;
+  }
+
+  /**
+   * <p>Sets the component in this tab that represents the focusable part of the
+   * tab.</p>
+   *
+   * <p><strong>Note:</strong> The focusable component must be part of this Tab</p>
+   *
+   * @param focusableComponent a component in this tab or null if no component
+   *                           should be focusable
+   */
+  public void setFocusableComponent(JComponent focusableComponent) {
+    if (this.focusableComponent != focusableComponent) {
+      boolean hasFocus = false;
+
+      if (this.focusableComponent != null) {
+        this.focusableComponent.removeKeyListener(focusableKeyListener);
+        hasFocus = this.focusableComponent.hasFocus();
+      }
+
+      this.focusableComponent = focusableComponent;
+      if (this.focusableComponent != null) {
+        this.focusableComponent.setFocusable(isSelected());
+        this.focusableComponent.addKeyListener(focusableKeyListener);
+        if (hasFocus)
+          this.focusableComponent.requestFocusInWindow();
+      }
+    }
+  }
+
+  /**
+   * <p>Gets the tab {@link Shape}.</p>
+   *
+   * <p>
+   * This returns the shape of the tab. This can be be used by for
+   * example content borders in the tabbed panel so they can skip a gap where the
+   * tab intersects the tabbed panel content area.
+   * </p>
+   *
+   * @return the tab {@link Shape}, null if the tab has the normal component rectangle shape
+   * @since ITP 1.2.0
+   */
+  public Shape getShape() {
+    return null;
+  }
+
+  /**
+   * Called by the tabbed panel when the tab becomes a member or is no longer a member of the
+   * tabbed panel
+   *
+   * @param tabbedPanel tabbed panel that this tab is a member of or null if this tab is no
+   *                    longer a member o a tabbed panel
+   */
+  protected void setTabbedPanel(TabbedPanel tabbedPanel) {
+    this.tabbedPanel = tabbedPanel;
+    if (this.tabbedPanel != null)
+      this.tabbedPanel.addTabListener(tabbedPanelListener);
+  }
+
+  DraggableComponent getDraggableComponent() {
+    return draggableComponent;
+  }
+
+  private void fireHighlightedEvent(TabStateChangedEvent event) {
+    if (listeners != null) {
+      TabStateChangedEvent e = new TabStateChangedEvent(this,
+                                                        event.getTabbedPanel(),
+                                                        this,
+                                                        event.getPreviousTab(),
+                                                        event.getCurrentTab());
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabHighlighted(e);
+    }
+  }
+
+  private void fireDehighlightedEvent(TabStateChangedEvent event) {
+    if (listeners != null) {
+      TabStateChangedEvent e = new TabStateChangedEvent(this,
+                                                        event.getTabbedPanel(),
+                                                        this,
+                                                        event.getPreviousTab(),
+                                                        event.getCurrentTab());
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabDehighlighted(e);
+    }
+  }
+
+  private void fireSelectedEvent(TabStateChangedEvent event) {
+    if (listeners != null) {
+      TabStateChangedEvent e = new TabStateChangedEvent(this,
+                                                        event.getTabbedPanel(),
+                                                        this,
+                                                        event.getPreviousTab(),
+                                                        event.getCurrentTab());
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabSelected(e);
+    }
+  }
+
+  private void fireDeselectedEvent(TabStateChangedEvent event) {
+    if (listeners != null) {
+      TabStateChangedEvent e = new TabStateChangedEvent(this,
+                                                        event.getTabbedPanel(),
+                                                        this,
+                                                        event.getPreviousTab(),
+                                                        event.getCurrentTab());
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabDeselected(e);
+    }
+  }
+
+  private void fireDraggedEvent(TabDragEvent event) {
+    if (listeners != null) {
+      TabDragEvent e = new TabDragEvent(this, event.getMouseEvent());
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabDragged(e);
+    }
+  }
+
+  private void fireDroppedEvent(TabDragEvent event) {
+    if (listeners != null) {
+      TabDragEvent e = new TabDragEvent(this, this, event.getPoint());
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabDropped(e);
+    }
+  }
+
+  private void fireNotDroppedEvent() {
+    if (listeners != null) {
+      TabEvent e = new TabEvent(this, this);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabDragAborted(e);
+    }
+  }
+
+  private void fireMovedEvent() {
+    if (listeners != null) {
+      TabEvent e = new TabEvent(this, this);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabMoved(e);
+    }
+  }
+
+  private void fireAddedEvent() {
+    if (listeners != null) {
+      TabEvent e = new TabEvent(this, this);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabAdded(e);
+    }
+  }
+
+  private void fireRemovedEvent(TabRemovedEvent event) {
+    if (listeners != null) {
+      TabRemovedEvent e = new TabRemovedEvent(this, this, event.getTabbedPanel());
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabRemoved(e);
+    }
+  }
+
+  public void addNotify() {
+    if (!draggableComponent.isIgnoreAddNotify())
+      super.addNotify();
+  }
+
+  public void removeNotify() {
+    if (!draggableComponent.isIgnoreAddNotify())
+      super.removeNotify();
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabAdapter.java b/src/net/infonode/tabbedpanel/TabAdapter.java
new file mode 100644
index 0000000..e2f63a9
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabAdapter.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabAdapter.java,v 1.9 2004/09/22 14:33:49 jesper Exp $
+package net.infonode.tabbedpanel;
+
+/**
+ * An adapter class for receiving events from a TabbedPanel or a Tab. The methods in this
+ * class are empty and it's purpose is to make it easier to create listeners when not all
+ * events are of interest.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ * @see TabbedPanel
+ */
+public class TabAdapter implements TabListener {
+  public void tabAdded(TabEvent event) {
+  }
+
+  public void tabRemoved(TabRemovedEvent event) {
+  }
+
+  public void tabDragged(TabDragEvent event) {
+  }
+
+  public void tabDropped(TabDragEvent event) {
+  }
+
+  public void tabDragAborted(TabEvent event) {
+  }
+
+  public void tabSelected(TabStateChangedEvent event) {
+  }
+
+  public void tabDeselected(TabStateChangedEvent event) {
+  }
+
+  public void tabHighlighted(TabStateChangedEvent event) {
+  }
+
+  public void tabDehighlighted(TabStateChangedEvent event) {
+  }
+
+  public void tabMoved(TabEvent event) {
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabAreaComponentsProperties.java b/src/net/infonode/tabbedpanel/TabAreaComponentsProperties.java
new file mode 100644
index 0000000..c130872
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabAreaComponentsProperties.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: TabAreaComponentsProperties.java,v 1.15 2005/02/16 11:28:15 jesper Exp $
+
+package net.infonode.tabbedpanel;
+
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+import net.infonode.properties.types.HoverListenerProperty;
+
+/**
+ * TabAreaComponentsProperties holds all visual properties for the area in a
+ * tabbed panel's tab area where the tab area components (scroll buttons, tab
+ * drop down list and components set by calling setTabAreaComponents in a tabbed
+ * panel) are shown. TabbedPanelProperties contains TabAreaComponentsProperties.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.15 $
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ * @since ITP 1.1.0
+ */
+public class TabAreaComponentsProperties extends PropertyMapContainer {
+  /**
+   * A property group for all properties in TabAreaComponentsProperties
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Tab Area Properties",
+                                                                         "Properties for the TabbedPanel class.");
+
+  /**
+   * Stretch enabled property
+   *
+   * @see #setStretchEnabled
+   * @see #getStretchEnabled
+   */
+  public static final BooleanProperty STRETCH_ENABLED = new BooleanProperty(PROPERTIES, "Stretch Enabled", "Stretch components to be as high as tabs if tabs are higher than components.",
+                                                                            PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Properties for the component
+   *
+   * @see #getComponentProperties
+   */
+  public static final PropertyMapProperty COMPONENT_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                         "Component Properties",
+                                                                                         "Properties for tab area components area.",
+                                                                                         ComponentProperties.PROPERTIES);
+
+  /**
+   * Properties for the shaped panel
+   *
+   * @see #getShapedPanelProperties
+   * @since ITP 1.2.0
+   */
+  public static final PropertyMapProperty SHAPED_PANEL_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                            "Shaped Panel Properties",
+                                                                                            "Properties for shaped tab area components area.",
+                                                                                            ShapedPanelProperties.PROPERTIES);
+
+  /**
+   * Hover listener property
+   *
+   * @see #setHoverListener
+   * @see #getHoverListener
+   * @since ITP 1.3.0
+   */
+  public static final HoverListenerProperty HOVER_LISTENER = new HoverListenerProperty(PROPERTIES,
+                                                                                       "Hover Listener",
+                                                                                       "Hover Listener to be used for tracking mouse hovering over the tab area components area.",
+                                                                                       PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Constructs an empty TabAreaComponentsProperties object
+   */
+  public TabAreaComponentsProperties() {
+    super(PROPERTIES);
+  }
+
+  /**
+   * Constructs a TabAreaComponentsProperties object with the given object as
+   * property storage
+   *
+   * @param object object to store properties in
+   */
+  public TabAreaComponentsProperties(PropertyMap object) {
+    super(object);
+  }
+
+  /**
+   * Constructs a TabAreaComponentsProperties object that inherits its properties
+   * from the given TabAreaComponentsProperties object
+   *
+   * @param inheritFrom TabAreaComponentsProperties object to inherit properties from
+   */
+  public TabAreaComponentsProperties(TabAreaComponentsProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param superObject the object from which to inherit property values
+   * @return this
+   */
+  public TabAreaComponentsProperties addSuperObject(TabAreaComponentsProperties superObject) {
+    getMap().addSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   */
+  public TabAreaComponentsProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes the given super object.
+   *
+   * @param superObject super object to remove
+   * @return this
+   * @since ITP 1.3.0
+   */
+  public TabAreaComponentsProperties removeSuperObject(TabAreaComponentsProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Gets if components should be stretched to same height as tabs if tabs are
+   * higher than components.
+   *
+   * @return true if stretch is enabled, otherwise false
+   */
+  public boolean getStretchEnabled() {
+    return STRETCH_ENABLED.get(getMap());
+  }
+
+  /**
+   * Sets if components should be stretched to same height as tabs if tabs are
+   * higher than components.
+   *
+   * @param enabled true for stretch, otherwise false
+   * @return this TabAreaComponentsProperties
+   */
+  public TabAreaComponentsProperties setStretchEnabled(boolean enabled) {
+    STRETCH_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Gets the component properties
+   *
+   * @return component properties
+   */
+  public ComponentProperties getComponentProperties() {
+    return new ComponentProperties(COMPONENT_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the shaped panel properties
+   *
+   * @return shaped panel properties
+   * @since ITP 1.2.0
+   */
+  public ShapedPanelProperties getShapedPanelProperties() {
+    return new ShapedPanelProperties(SHAPED_PANEL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * <p>Sets the hover listener that will be triggered when the tab area components area is hoverd by the mouse.</p>
+   *
+   * <p>The tabbed panel that the hovered tab area components area is part of will be the source of the hover event
+   * sent to the hover listener.</p>
+   *
+   * @param listener the hover listener
+   * @return this TabAreaComponentsProperties
+   * @since ITP 1.3.0
+   */
+  public TabAreaComponentsProperties setHoverListener(HoverListener listener) {
+    HOVER_LISTENER.set(getMap(), listener);
+    return this;
+  }
+
+  /**
+   * <p>Gets the hover listener that will be triggered when the tab area components area is hovered by the mouse.</p>
+   *
+   * <p>The tabbed panel that the hovered tab area components area is part of will be the source of the hover event
+   * sent to the hover listener.</p>
+   *
+   * @return the hover listener
+   * @since ITP 1.3.0
+   */
+  public HoverListener getHoverListener() {
+    return HOVER_LISTENER.get(getMap());
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/TabAreaProperties.java b/src/net/infonode/tabbedpanel/TabAreaProperties.java
new file mode 100644
index 0000000..c09116d
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabAreaProperties.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabAreaProperties.java,v 1.28 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.HoverListenerProperty;
+
+/**
+ * TabAreaProperties holds all visual properties for a tabbed panel's tab area.
+ * TabbedPanelProperties contains TabAreaProperties.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.28 $
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ */
+public class TabAreaProperties extends PropertyMapContainer {
+  /**
+   * A property group for all properties in TabAreaProperties
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Tab Area Properties",
+                                                                         "Properties for the TabbedPanel class.");
+
+  /**
+   * Properties for the component
+   *
+   * @see #getComponentProperties
+   */
+  public static final PropertyMapProperty COMPONENT_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                         "Component Properties",
+                                                                                         "Properties for tab area component.",
+                                                                                         ComponentProperties.PROPERTIES);
+
+  /**
+   * Properties for the shaped panel
+   *
+   * @see #getShapedPanelProperties
+   * @since ITP 1.2.0
+   */
+  public static final PropertyMapProperty SHAPED_PANEL_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                            "Shaped Panel Properties",
+                                                                                            "Properties for shaped tab area.",
+                                                                                            ShapedPanelProperties.PROPERTIES);
+
+  /**
+   * Hover listener property
+   *
+   * @see #setHoverListener
+   * @see #getHoverListener
+   * @since ITP 1.3.0
+   */
+  public static final HoverListenerProperty HOVER_LISTENER = new HoverListenerProperty(PROPERTIES,
+                                                                                       "Hover Listener",
+                                                                                       "Hover Listener to be used for tracking mouse hovering over the tab area components area.",
+                                                                                       PropertyMapValueHandler.INSTANCE);
+  /**
+   * Tab area visible property
+   *
+   * @see #setTabAreaVisiblePolicy(TabAreaVisiblePolicy)
+   * @see #getTabAreaVisiblePolicy()
+   * @since ITP 1.4.0
+   */
+  public static final TabAreaVisiblePolicyProperty TAB_AREA_VISIBLE_POLICY = new TabAreaVisiblePolicyProperty(
+      PROPERTIES,
+      "Visible Policy",
+      "Visiblity for the tab area.",
+      PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Constructs an empty TabAreaProperties object
+   */
+  public TabAreaProperties() {
+    super(PROPERTIES);
+  }
+
+  /**
+   * Constructs a TabAreaProperties object with the given object
+   * as property storage
+   *
+   * @param object object to store properties in
+   */
+  public TabAreaProperties(PropertyMap object) {
+    super(object);
+  }
+
+  /**
+   * Constructs a TabAreaProperties object that inherits its properties
+   * from the given TabAreaProperties object
+   *
+   * @param inheritFrom TabAreaProperties object to inherit properties from
+   */
+  public TabAreaProperties(TabAreaProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param superObject the object from which to inherit property values
+   * @return this
+   */
+  public TabAreaProperties addSuperObject(TabAreaProperties superObject) {
+    getMap().addSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   */
+  public TabAreaProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes the given super object.
+   *
+   * @param superObject super object to remove
+   * @return this
+   * @since ITP 1.3.0
+   */
+  public TabAreaProperties removeSuperObject(TabAreaProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Gets the component properties
+   *
+   * @return component properties
+   */
+  public ComponentProperties getComponentProperties() {
+    return new ComponentProperties(COMPONENT_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the shaped panel properties
+   *
+   * @return shaped panel properties
+   * @since ITP 1.2.0
+   */
+  public ShapedPanelProperties getShapedPanelProperties() {
+    return new ShapedPanelProperties(SHAPED_PANEL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * <p>Sets the hover listener that will be triggered when the tab area is hoverd by the mouse.</p>
+   *
+   * <p>The tabbed panel that the hovered tab area is part of will be the source of the hover event sent to the
+   * hover listener.</p>
+   *
+   * @param listener the hover listener
+   * @return this TabAreaProperties
+   * @since ITP 1.3.0
+   */
+  public TabAreaProperties setHoverListener(HoverListener listener) {
+    HOVER_LISTENER.set(getMap(), listener);
+    return this;
+  }
+
+  /**
+   * <p>Sets the hover listener that will be triggered when the tab area is hovered by the mouse.</p>
+   *
+   * <p>The tabbed panel that the hovered tab area is part of will be the source of the hover event sent to the
+   * hover listener.</p>
+   *
+   * @return the hover listener
+   * @since ITP 1.3.0
+   */
+  public HoverListener getHoverListener() {
+    return HOVER_LISTENER.get(getMap());
+  }
+
+  /**
+   * Sets the tab area visible policy for the tab area, i.e. when the tab area is to be visible
+   *
+   * @param policy the tab area visible policy
+   * @return this TabAreaProperties
+   * @since ITP 1.4.0
+   */
+  public TabAreaProperties setTabAreaVisiblePolicy(TabAreaVisiblePolicy policy) {
+    TAB_AREA_VISIBLE_POLICY.set(getMap(), policy);
+    return this;
+  }
+
+  /**
+   * Gets the tab area visible policy for the tab area, i.e. when the tab area is to be visible
+   *
+   * @return the tab area visible policy
+   * @since ITP 1.4.0
+   */
+  public TabAreaVisiblePolicy getTabAreaVisiblePolicy() {
+    return TAB_AREA_VISIBLE_POLICY.get(getMap());
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabAreaVisiblePolicy.java b/src/net/infonode/tabbedpanel/TabAreaVisiblePolicy.java
new file mode 100644
index 0000000..352b053
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabAreaVisiblePolicy.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabAreaVisiblePolicy.java,v 1.3 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.util.Enum;
+
+/**
+ * TabAreaVisiblePolicy defines the visibility policies for the tab area of a tabbed panel.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ * @since ITP 1.4.0
+ */
+public class TabAreaVisiblePolicy extends Enum {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * Always visible policy. This means that the tab area is always visible.
+   */
+  public static final TabAreaVisiblePolicy ALWAYS = new TabAreaVisiblePolicy(0, "Always");
+
+  /**
+   * Never visible policy. This means that the tab area is never visible.
+   */
+  public static final TabAreaVisiblePolicy NEVER = new TabAreaVisiblePolicy(1, "Never");
+
+  /**
+   * Tabs exist visible policy. This means that the tab area will only be visible if it contains tabs.
+   */
+  public static final TabAreaVisiblePolicy TABS_EXIST = new TabAreaVisiblePolicy(2, "Tabs Exist in Tab Area");
+
+  /**
+   * More than one visible policy. This means that the tab area is visible when the tabbed
+   * panel contains more than one tab.
+   */
+  public static final TabAreaVisiblePolicy MORE_THAN_ONE_TAB = new TabAreaVisiblePolicy(3, "More than One Tab");
+
+  private static final TabAreaVisiblePolicy[] VISIBLE_POLICIES = new TabAreaVisiblePolicy[]{ALWAYS, NEVER, TABS_EXIST, MORE_THAN_ONE_TAB};
+
+  private TabAreaVisiblePolicy(int value, String name) {
+    super(value, name);
+  }
+
+  /**
+   * Gets the tab area visible policies.
+   *
+   * @return the tab layout policies
+   */
+  public static TabAreaVisiblePolicy[] getVisiblePolicies() {
+    return (TabAreaVisiblePolicy[]) VISIBLE_POLICIES.clone();
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabAreaVisiblePolicyProperty.java b/src/net/infonode/tabbedpanel/TabAreaVisiblePolicyProperty.java
new file mode 100644
index 0000000..f946c10
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabAreaVisiblePolicyProperty.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabAreaVisiblePolicyProperty.java,v 1.2 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.types.EnumProperty;
+import net.infonode.properties.util.PropertyValueHandler;
+
+/**
+ * Property for TabAreaVisiblePolicy
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ * @see net.infonode.tabbedpanel.TabAreaVisiblePolicy
+ * @since ITP 1.4.0
+ */
+public class TabAreaVisiblePolicyProperty extends EnumProperty {
+  /**
+   * Constructs a TabAreaVisiblePolicyProperty object
+   *
+   * @param group        property group
+   * @param name         property name
+   * @param description  property description
+   * @param valueStorage storage for property
+   */
+  public TabAreaVisiblePolicyProperty(PropertyGroup group,
+                                      String name,
+                                      String description,
+                                      PropertyValueHandler valueStorage) {
+    super(group,
+          name,
+          TabAreaVisiblePolicy.class,
+          description,
+          valueStorage,
+          TabAreaVisiblePolicy.getVisiblePolicies());
+  }
+
+  /**
+   * Gets the TabAreaVisiblePolicy
+   *
+   * @param object storage object for property
+   * @return the TabAreaVisiblePolicy
+   */
+  public TabAreaVisiblePolicy get(Object object) {
+    return (TabAreaVisiblePolicy) getValue(object);
+  }
+
+  /**
+   * Sets the TabAreaVisiblePolicy
+   *
+   * @param object storage object for property
+   * @param policy the TabAreaVisiblePolicy
+   */
+  public void set(Object object, TabAreaVisiblePolicy policy) {
+    setValue(object, policy);
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabContentPanel.java b/src/net/infonode/tabbedpanel/TabContentPanel.java
new file mode 100644
index 0000000..d17d9ea
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabContentPanel.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabContentPanel.java,v 1.19 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.gui.layout.StackableLayout;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * A TabContentPanel is a container for tabs' content components. It listens to
+ * a tabbed panel and manages the tabs' content components by showing and hiding
+ * the components based upon the selection of tabs in the tabbed panel.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.19 $
+ * @see TabbedPanel
+ * @see Tab
+ */
+public class TabContentPanel extends JPanel {
+  private TabbedPanel tabbedPanel;
+  private StackableLayout layout = new StackableLayout(this);
+  private TabListener listener = new TabAdapter() {
+    public void tabSelected(TabStateChangedEvent event) {
+      layout.showComponent(event.getTab() == null ? null : event.getTab().getContentComponent());
+    }
+
+    public void tabRemoved(TabRemovedEvent event) {
+      if (event.getTab().getContentComponent() != null)
+        remove(event.getTab().getContentComponent());
+    }
+
+    public void tabAdded(TabEvent event) {
+      if (event.getTab().getContentComponent() != null)
+        add(event.getTab().getContentComponent());
+    }
+  };
+
+  /**
+   * <p>
+   * Constructs a TabContentPanel
+   * </p>
+   *
+   * <p><strong>Note:</strong> setTabbedPanel(...) must be called before the tabs'
+   * content components can be shown on the screen.
+   * </p>
+   *
+   * @since ITP 1.4.0
+   */
+  public TabContentPanel() {
+    setLayout(layout);
+    setOpaque(false);
+    layout.setAutoShowFirstComponent(false);
+  }
+
+  /**
+   * Constructs a TabContentPanel
+   *
+   * @param tabbedPanel the TabbedPanel for whom this component is the tabs' content
+   *                    component container
+   */
+  public TabContentPanel(TabbedPanel tabbedPanel) {
+    this();
+    setTabbedPanel(tabbedPanel);
+  }
+
+  /**
+   * Gets the TabbedPanel for whom this component is the tabs' content component
+   * container
+   *
+   * @return the TabbedPanel
+   */
+  public TabbedPanel getTabbedPanel() {
+    return tabbedPanel;
+  }
+
+  /**
+   * Sets the TabbedPanel
+   *
+   * @param tabbedPanel the TabbedPanel for whom this component is the tabs' content
+   *                    component container
+   * @since ITP 1.4.0
+   */
+  public void setTabbedPanel(TabbedPanel tabbedPanel) {
+    if (this.tabbedPanel != tabbedPanel) {
+      if (this.tabbedPanel != null) {
+        this.tabbedPanel.removeTabListener(listener);
+        removeAll();
+      }
+
+      this.tabbedPanel = tabbedPanel;
+
+      if (this.tabbedPanel != null) {
+        tabbedPanel.addTabListener(listener);
+        for (int i = 0; i < tabbedPanel.getTabCount(); i++) {
+          Component c = tabbedPanel.getTabAt(i).getContentComponent();
+          if (c != null)
+            add(tabbedPanel.getTabAt(i).getContentComponent());
+        }
+      }
+    }
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabDepthOrderPolicy.java b/src/net/infonode/tabbedpanel/TabDepthOrderPolicy.java
new file mode 100644
index 0000000..41afa2d
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabDepthOrderPolicy.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabDepthOrderPolicy.java,v 1.4 2004/11/11 14:10:33 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.util.Enum;
+
+/**
+ * TabDepthOrderPolicy defines the depth order for the tabs in a tabbed panel's
+ * tab area when tab spacing is negative i.e. the tabs are overlapping.
+ *
+ * @author johan
+ * @version $Revision: 1.4 $
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ * @since ITP 1.2.0
+ */
+public class TabDepthOrderPolicy extends Enum {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * Descending depth order policy. This means that the first tab will be the
+   * top most and the last tab will be the bottom most. Note that if a tab is
+   * highlighted, it will always be on top of the other tabs.
+   */
+  public static final TabDepthOrderPolicy DESCENDING = new TabDepthOrderPolicy(0, "Descending");
+
+  /**
+   * Ascending depth order policy. This means that the first tab will be the
+   * bottom most and the last tab will be the top most. Note that if a tab is
+   * highlighted, it will always be on top of the other tabs.
+   */
+  public static final TabDepthOrderPolicy ASCENDING = new TabDepthOrderPolicy(1, "Ascending");
+
+  private TabDepthOrderPolicy(int value, String name) {
+    super(value, name);
+  }
+
+  /**
+   * Gets the available tab depth order policies.
+   *
+   * @return the tab depth order policies
+   */
+  public static TabDepthOrderPolicy[] getDepthOrderPolicies() {
+    return new TabDepthOrderPolicy[]{DESCENDING, ASCENDING};
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabDepthOrderPolicyProperty.java b/src/net/infonode/tabbedpanel/TabDepthOrderPolicyProperty.java
new file mode 100644
index 0000000..5685168
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabDepthOrderPolicyProperty.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabDepthOrderPolicyProperty.java,v 1.4 2005/02/16 11:28:15 jesper Exp $
+
+package net.infonode.tabbedpanel;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.types.EnumProperty;
+import net.infonode.properties.util.PropertyValueHandler;
+
+/**
+ * Property for TabDepthOrderPolicy
+ *
+ * @author johan
+ * @version $Revision: 1.4 $
+ * @see TabDepthOrderPolicy
+ * @since ITP 1.2.0
+ */
+public class TabDepthOrderPolicyProperty extends EnumProperty {
+  /**
+   * Constructs a TabDepothOrderPolicyProperty object
+   *
+   * @param group        property group
+   * @param name         property name
+   * @param description  property description
+   * @param valueStorage storage for property
+   */
+  public TabDepthOrderPolicyProperty(PropertyGroup group,
+                                     String name,
+                                     String description,
+                                     PropertyValueHandler valueStorage) {
+    super(group,
+          name,
+          TabDepthOrderPolicy.class,
+          description,
+          valueStorage,
+          TabDepthOrderPolicy.getDepthOrderPolicies());
+  }
+
+  /**
+   * Gets the TabDepthOrderPolicy
+   *
+   * @param object storage object for property
+   * @return the TabDepthOrderPolicy
+   */
+  public TabDepthOrderPolicy get(Object object) {
+    return (TabDepthOrderPolicy) getValue(object);
+  }
+
+  /**
+   * Sets the TabDepthOrderPolicy
+   *
+   * @param object storage object for property
+   * @param policy the TabDepthOrderPolicy
+   */
+  public void set(Object object, TabDepthOrderPolicy policy) {
+    setValue(object, policy);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/TabDragEvent.java b/src/net/infonode/tabbedpanel/TabDragEvent.java
new file mode 100644
index 0000000..23aac71
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabDragEvent.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabDragEvent.java,v 1.13 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+
+/**
+ * TabDragEvent is an mouseEvent that contains information about the tab that is
+ * beeing dragged from a tabbed panel and a point specifying the mouse
+ * coordinates.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.13 $
+ * @see TabbedPanel
+ * @see Tab
+ */
+public class TabDragEvent extends TabEvent {
+  private MouseEvent mouseEvent;
+
+  /**
+   * Constructs a TabDragEvent
+   *
+   * @param source the Tab or TabbedPanel that is the source for this
+   *               mouseEvent
+   * @param tab    the Tab that is being dragged
+   * @param point  the mouse coordinates relative to the Tab that is being
+   *               dragged
+   * @deprecated Use {@link #TabDragEvent(Object, java.awt.event.MouseEvent)} instead.
+   */
+  public TabDragEvent(Object source, Tab tab, Point point) {
+    this(source,
+         new MouseEvent(tab, MouseEvent.MOUSE_DRAGGED, System.currentTimeMillis(), 0, point.x, point.y, 0, false));
+  }
+
+  /**
+   * Constructs a TabDragEvent
+   *
+   * @param source     the Tab or TabbedPanel that is the source for this
+   * @param mouseEvent the mouse mouseEvent that triggered the drag, the event source should be the tab and the event
+   *                   point should be relative to the tab
+   * @since ITP 1.3.0
+   */
+  public TabDragEvent(Object source, MouseEvent mouseEvent) {
+    super(source, (Tab) mouseEvent.getSource());
+    this.mouseEvent = mouseEvent;
+  }
+
+  /**
+   * Gets the mouse coordinates
+   *
+   * @return the mouse coordinats relative to the Tab that is beeing
+   *         dragged
+   */
+  public Point getPoint() {
+    return mouseEvent.getPoint();
+  }
+
+  /**
+   * Returns the mouse event that triggered this drag. The event source is set to the tab and the event point is
+   * relative to the tab.
+   *
+   * @return the mouse event that triggered this drag
+   * @since ITP 1.3.0
+   */
+  public MouseEvent getMouseEvent() {
+    return mouseEvent;
+  }
+
+}
diff --git a/src/net/infonode/tabbedpanel/TabDropDownListVisiblePolicy.java b/src/net/infonode/tabbedpanel/TabDropDownListVisiblePolicy.java
new file mode 100644
index 0000000..931b05e
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabDropDownListVisiblePolicy.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabDropDownListVisiblePolicy.java,v 1.11 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.util.Enum;
+
+/**
+ * TabDropDownListVisiblePolicy tells the tabbed panel when to show a drop down
+ * list of tabs.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.11 $
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ * @since ITP 1.1.0
+ */
+public final class TabDropDownListVisiblePolicy extends Enum {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * Never drop down list policy. This means that no drop down list will be shown
+   * in the tabbed panel.
+   */
+  public static final TabDropDownListVisiblePolicy NEVER = new TabDropDownListVisiblePolicy(0, "Never");
+
+  /**
+   * More than one tab list policy. This means that a drop down list will be shown
+   * if there are more than one tab in the tabbed panel.
+   */
+  public static final TabDropDownListVisiblePolicy MORE_THAN_ONE_TAB = new TabDropDownListVisiblePolicy(1,
+                                                                                                        "More than One Tab");
+
+  /**
+   * Tabs not visible list policy. This means that a drop down list will be shown when
+   * there are tabs are not entirely visible, i.e. scrolled out.
+   */
+  public static final TabDropDownListVisiblePolicy TABS_NOT_VISIBLE = new TabDropDownListVisiblePolicy(1,
+                                                                                                       "Some Tabs Not Visible");
+
+  private static final TabDropDownListVisiblePolicy[] DROP_DOWN_LIST_VISIBLE_POLICIES = new TabDropDownListVisiblePolicy[]{
+    NEVER, MORE_THAN_ONE_TAB, TABS_NOT_VISIBLE};
+
+  private TabDropDownListVisiblePolicy(int value, String name) {
+    super(value, name);
+  }
+
+  /**
+   * Gets the tab drop down list visible policies.
+   *
+   * @return the tab drop down list visible policies
+   */
+  public static TabDropDownListVisiblePolicy[] getDropDownListVisiblePolicies() {
+    return (TabDropDownListVisiblePolicy[]) DROP_DOWN_LIST_VISIBLE_POLICIES.clone();
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabDropDownListVisiblePolicyProperty.java b/src/net/infonode/tabbedpanel/TabDropDownListVisiblePolicyProperty.java
new file mode 100644
index 0000000..31daafa
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabDropDownListVisiblePolicyProperty.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabDropDownListVisiblePolicyProperty.java,v 1.7 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.types.EnumProperty;
+import net.infonode.properties.util.PropertyValueHandler;
+
+/**
+ * Property for TabDropDownListVisiblePolicy
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ * @see TabDropDownListVisiblePolicy
+ * @since ITP 1.1.0
+ */
+public class TabDropDownListVisiblePolicyProperty extends EnumProperty {
+  /**
+   * Constructs a TabDropDownListVisiblePolicyProperty object
+   *
+   * @param group        property group
+   * @param name         property name
+   * @param description  property description
+   * @param valueStorage storage for property
+   */
+  public TabDropDownListVisiblePolicyProperty(PropertyGroup group,
+                                              String name,
+                                              String description,
+                                              PropertyValueHandler valueStorage) {
+    super(group,
+          name,
+          TabDropDownListVisiblePolicy.class,
+          description,
+          valueStorage,
+          TabDropDownListVisiblePolicy.getDropDownListVisiblePolicies());
+  }
+
+  /**
+   * Gets the TabDropDownListVisiblePolicy
+   *
+   * @param object storage object for property
+   * @return the TabDropDownListVisiblePolicy
+   */
+  public TabDropDownListVisiblePolicy get(Object object) {
+    return (TabDropDownListVisiblePolicy) getValue(object);
+  }
+
+  /**
+   * Sets the TabDropDownListVisiblePolicy
+   *
+   * @param object storage object for property
+   * @param policy the TabDropDownListVisiblePolicy
+   */
+  public void set(Object object, TabDropDownListVisiblePolicy policy) {
+    setValue(object, policy);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/TabEvent.java b/src/net/infonode/tabbedpanel/TabEvent.java
new file mode 100644
index 0000000..f04a9da
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabEvent.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabEvent.java,v 1.10 2004/09/22 14:33:49 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import java.util.EventObject;
+
+/**
+ * TabEvent is the root event for all tab events. It contains
+ * information about the source, i.e. the object (tab or tabbedPanel)
+ * that generated this event and the tab that was affected by this event.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ * @see TabListener
+ * @see TabbedPanel
+ * @see Tab
+ */
+public class TabEvent extends EventObject {
+  private Tab tab;
+
+  /**
+   * Constructs a TabEvent
+   *
+   * @param source the Tab or TabbedPanel that is the source for this
+   *               event
+   * @param tab    the tab on which the event occurred
+   */
+  TabEvent(Object source, Tab tab) {
+    super(source);
+    this.tab = tab;
+  }
+
+  /**
+   * Gets the tab that is the source for this event
+   *
+   * @return the Tab affected by this event or null if no tab
+   *         was affected
+   */
+  public Tab getTab() {
+    return tab;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabFactory.java b/src/net/infonode/tabbedpanel/TabFactory.java
new file mode 100644
index 0000000..f020e26
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabFactory.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabFactory.java,v 1.10 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+
+import javax.swing.*;
+
+/**
+ * Factory methods for creating different tabs
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ * @see Tab
+ * @see TitledTab
+ */
+public class TabFactory {
+  private TabFactory() {
+  }
+
+  /**
+   * Creates a TitledTab with a text and an icon
+   *
+   * @param text the text
+   * @param icon the icon or null for no icon
+   * @return the created TitledTab
+   */
+  public static TitledTab createTitledTab(String text, Icon icon) {
+    return new TitledTab(text, icon, null, null);
+  }
+
+  /**
+   * Creates a TitledTab with a text, an icon and a content component
+   *
+   * @param text             the text
+   * @param icon             the icon or null for no icon
+   * @param contentComponent the content component for the tab
+   * @return the created TitledTab
+   */
+  public static TitledTab createTitledTab(String text, Icon icon, JComponent contentComponent) {
+    return new TitledTab(text, icon, contentComponent, null);
+  }
+
+  /**
+   * Creates a TitledTab with a text, an icon, a title component and a
+   * content component
+   *
+   * @param text             the text
+   * @param icon             the icon or null for no icon
+   * @param contentComponent the content component for the tab
+   * @param titleComponent   the title component for the tab
+   * @return the created TitledTab
+   */
+  public static TitledTab createTitledTab(String text, Icon icon, JComponent contentComponent,
+                                          JComponent titleComponent) {
+    return new TitledTab(text, icon, contentComponent, titleComponent);
+  }
+
+  /**
+   * Creates a TitledTab with a text, a different icon for each of the
+   * states, a title component and a content component
+   *
+   * @param text             the text
+   * @param icon             the icon for the normal state or null for no icon
+   * @param highlightedIcon  the icon for the highlighted state or null fo no icon
+   * @param disabledIcon     the icon for the disabled state or null for no icon
+   * @param contentComponent the content component for the tab
+   * @param titleComponent   the title component for the tab
+   * @return the created TitledTab
+   */
+  public static TitledTab createTitledTab(String text, Icon icon, Icon highlightedIcon, Icon disabledIcon,
+                                          JComponent contentComponent, JComponent titleComponent) {
+    TitledTab tab = new TitledTab(text, icon, contentComponent, titleComponent);
+    tab.getProperties().getHighlightedProperties().setIcon(highlightedIcon);
+    tab.getProperties().getDisabledProperties().setIcon(disabledIcon);
+    return tab;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabLayoutPolicy.java b/src/net/infonode/tabbedpanel/TabLayoutPolicy.java
new file mode 100644
index 0000000..ec4d76f
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabLayoutPolicy.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabLayoutPolicy.java,v 1.9 2004/09/28 15:07:29 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.util.Enum;
+
+/**
+ * TabLayoutPolicy defines how the tabs in a tabbed panel's tab area can be laid out.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ */
+public final class TabLayoutPolicy extends Enum {
+  private static final long serialVersionUID = -1345037155950998515L;
+
+  /**
+   * Scrolling layout policy. This means that the tabs are laid out in a line. The
+   * line of tabs will be scrollable if not all tabs can fit into the visible part
+   * of the tabbed panel's tab area at the same time.
+   */
+  public static final TabLayoutPolicy SCROLLING = new TabLayoutPolicy(0, "Scrolling");
+
+  /**
+   * Compression layout policy. This means that the tabs are laid out in a line. The
+   * tabs will be downsized (compressed) so that they fit into the visible part of the
+   * tab area.
+   */
+  public static final TabLayoutPolicy COMPRESSION = new TabLayoutPolicy(1, "Compression");
+
+  /**
+   * Array with all available layout policies.
+   */
+  public static final TabLayoutPolicy[] LAYOUT_POLICIES = new TabLayoutPolicy[]{SCROLLING, COMPRESSION};
+
+  private TabLayoutPolicy(int value, String name) {
+    super(value, name);
+  }
+
+  /**
+   * Gets the tab layout policies.
+   *
+   * @return the tab layout policies
+   * @since ITP 1.1.0
+   */
+  public static TabLayoutPolicy[] getLayoutPolicies() {
+    return (TabLayoutPolicy[]) LAYOUT_POLICIES.clone();
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabLayoutPolicyProperty.java b/src/net/infonode/tabbedpanel/TabLayoutPolicyProperty.java
new file mode 100644
index 0000000..5fe17b6
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabLayoutPolicyProperty.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabLayoutPolicyProperty.java,v 1.9 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.types.EnumProperty;
+import net.infonode.properties.util.PropertyValueHandler;
+
+/**
+ * Property for TabLayoutPolicy
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ * @see TabLayoutPolicy
+ */
+public class TabLayoutPolicyProperty extends EnumProperty {
+  /**
+   * Constructs a TabLayoutPolicyProperty object
+   *
+   * @param group        property group
+   * @param name         property name
+   * @param description  property description
+   * @param valueStorage storage for property
+   */
+  public TabLayoutPolicyProperty(PropertyGroup group,
+                                 String name,
+                                 String description,
+                                 PropertyValueHandler valueStorage) {
+    super(group, name, TabLayoutPolicy.class, description, valueStorage, TabLayoutPolicy.getLayoutPolicies());
+  }
+
+  /**
+   * Gets the TabLayoutPolicy
+   *
+   * @param object storage object for property
+   * @return the TabLayoutPolicy
+   */
+  public TabLayoutPolicy get(Object object) {
+    return (TabLayoutPolicy) getValue(object);
+  }
+
+  /**
+   * Sets the TabLayoutPolicy
+   *
+   * @param object storage object for property
+   * @param policy the TabLayoutPolicy
+   */
+  public void set(Object object, TabLayoutPolicy policy) {
+    setValue(object, policy);
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabListener.java b/src/net/infonode/tabbedpanel/TabListener.java
new file mode 100644
index 0000000..de1dce8
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabListener.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabListener.java,v 1.10 2004/09/22 14:33:49 jesper Exp $
+package net.infonode.tabbedpanel;
+
+/**
+ * <p>TabListener interface for receiving events from a TabbedPanel or a Tab.</p>
+ *
+ * <p>Adding a TabListener to a tabbed panel or a tab makes it possible to receive
+ * events when a tab component is added, removed, moved, highlighted, dehighlighted,
+ * selected, deselected, dragged, dropped or drag aborted.</p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ * @see TabbedPanel
+ * @see Tab
+ */
+public interface TabListener {
+  /**
+   * Called when a tab is added or inserted to a TabbedPanel
+   *
+   * @param event the event
+   */
+  void tabAdded(TabEvent event);
+
+  /**
+   * Called when a tab is removed from a TabbedPanel
+   *
+   * @param event the event
+   */
+  void tabRemoved(TabRemovedEvent event);
+
+  /**
+   * Called when a tab is dragged.
+   *
+   * @param event the event
+   */
+  void tabDragged(TabDragEvent event);
+
+  /**
+   * Called when a tab is dropped.
+   *
+   * @param event the event
+   */
+  void tabDropped(TabDragEvent event);
+
+  /**
+   * Called when an ongoing tab drag is aborted.
+   *
+   * @param event the event
+   */
+  void tabDragAborted(TabEvent event);
+
+  /**
+   * Called when a tab is selected
+   *
+   * @param event the event
+   */
+  void tabSelected(TabStateChangedEvent event);
+
+  /**
+   * <p>Called when a tab is deselected.</p>
+   *
+   * <p><strong>Note:</strong> The event contains information about the previously
+   * selected tab and the current selected tab.</p>
+   *
+   * @param event the event
+   */
+  void tabDeselected(TabStateChangedEvent event);
+
+  /**
+   * Called when a tab is highlighted
+   *
+   * @param event the event
+   */
+  void tabHighlighted(TabStateChangedEvent event);
+
+  /**
+   * <p>Called when a tab is dehighlighted.</p>
+   *
+   * <p><strong>Note:</strong> The event contains information about the previously
+   * highlighted tab and the current selected tab.</p>
+   *
+   * @param event the event
+   */
+  void tabDehighlighted(TabStateChangedEvent event);
+
+  /**
+   * Called when a tab is moved, i.e. dragged to another position in
+   * the tab area
+   *
+   * @param event the event
+   */
+  void tabMoved(TabEvent event);
+}
diff --git a/src/net/infonode/tabbedpanel/TabRemovedEvent.java b/src/net/infonode/tabbedpanel/TabRemovedEvent.java
new file mode 100644
index 0000000..7a4b8cf
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabRemovedEvent.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabRemovedEvent.java,v 1.4 2004/09/22 14:33:49 jesper Exp $
+package net.infonode.tabbedpanel;
+
+/**
+ * TabRemovedEvent is an event that contains information about the tab that was
+ * removed from a tabbed panel and the tabbed panel it was removed from.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ * @see TabbedPanel
+ * @see Tab
+ */
+public class TabRemovedEvent extends TabEvent {
+  private TabbedPanel tabbedPanel;
+
+  /**
+   * Constructs a TabDragEvent
+   *
+   * @param source      the Tab ot TabbedPanel that is the source for this
+   *                    event
+   * @param tab         the Tab that was removed
+   * @param tabbedPanel the TabbedPanel that the Tab was removed from
+   */
+  public TabRemovedEvent(Object source, Tab tab, TabbedPanel tabbedPanel) {
+    super(source, tab);
+    this.tabbedPanel = tabbedPanel;
+  }
+
+  /**
+   * Gets the TabbedPanel the Tab was removed from
+   *
+   * @return the TabbedPanel the Tab was removed from
+   */
+  public TabbedPanel getTabbedPanel() {
+    return tabbedPanel;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabSelectTrigger.java b/src/net/infonode/tabbedpanel/TabSelectTrigger.java
new file mode 100644
index 0000000..6585990
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabSelectTrigger.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabSelectTrigger.java,v 1.7 2004/09/28 14:50:49 jesper Exp $
+
+package net.infonode.tabbedpanel;
+
+import net.infonode.util.Enum;
+
+/**
+ * TabSelectTrigger defines what triggers a tab selection in a TabbedPanel.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ * @since ITP 1.1.0
+ */
+public final class TabSelectTrigger extends Enum {
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * Mouse press select trigger. This means that a tab will be selected on
+   * mouse pressed (button down).
+   */
+  public static final TabSelectTrigger MOUSE_PRESS = new TabSelectTrigger(0, "Mouse Press");
+
+  /**
+   * Mouse release select trigger. This means that a tab will be selected on
+   * mouse release (button up).
+   */
+  public static final TabSelectTrigger MOUSE_RELEASE = new TabSelectTrigger(1, "Mouse Release");
+
+  private static final TabSelectTrigger[] SELECT_TRIGGERS = {MOUSE_PRESS, MOUSE_RELEASE};
+
+  private TabSelectTrigger(int value, String name) {
+    super(value, name);
+  }
+
+  /**
+   * Gets the tab select triggers.
+   *
+   * @return the tab select triggers
+   */
+  public static TabSelectTrigger[] getSelectTriggers() {
+    return (TabSelectTrigger[]) SELECT_TRIGGERS.clone();
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/TabSelectTriggerProperty.java b/src/net/infonode/tabbedpanel/TabSelectTriggerProperty.java
new file mode 100644
index 0000000..70f8672
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabSelectTriggerProperty.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabSelectTriggerProperty.java,v 1.8 2005/02/16 11:28:15 jesper Exp $
+
+package net.infonode.tabbedpanel;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.types.EnumProperty;
+import net.infonode.properties.util.PropertyValueHandler;
+
+/**
+ * Property for TabSelectTrigger
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ * @since ITP 1.1.0
+ */
+public class TabSelectTriggerProperty extends EnumProperty {
+  /**
+   * Constructs a TabSelectTriggerProperty object
+   *
+   * @param group        property group
+   * @param name         property name
+   * @param description  property description
+   * @param valueStorage storage for property
+   */
+  public TabSelectTriggerProperty(PropertyGroup group,
+                                  String name,
+                                  String description,
+                                  PropertyValueHandler valueStorage) {
+    super(group, name, TabSelectTrigger.class, description, valueStorage, TabSelectTrigger.getSelectTriggers());
+  }
+
+  /**
+   * Gets the TabSelectTrigger
+   *
+   * @param object storage object for property
+   * @return the TabSelectTrigger
+   */
+  public TabSelectTrigger get(Object object) {
+    return (TabSelectTrigger) getValue(object);
+  }
+
+  /**
+   * Sets the TabSelectTrigger
+   *
+   * @param object  storage object for property
+   * @param trigger the TabSelectTrigger
+   */
+  public void set(Object object, TabSelectTrigger trigger) {
+    setValue(object, trigger);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/TabStateChangedEvent.java b/src/net/infonode/tabbedpanel/TabStateChangedEvent.java
new file mode 100644
index 0000000..afacd3b
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabStateChangedEvent.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabStateChangedEvent.java,v 1.5 2004/09/22 14:33:49 jesper Exp $
+package net.infonode.tabbedpanel;
+
+/**
+ * <p>TabStateChangedEvent is a state changed event. A change could mean that the
+ * selected tab has been deselcted and another tab has been selected.</p>
+ *
+ * <p>Example:  Tab 1 is the selected tab. The user selects tab 2 and tab 1 will be
+ * deselected. A change event will then be triggered where tab 1 will
+ * be the previous tab (getPreviousTab()) and tab 2 will be the curent
+ * tab (getCurrentTab()).</p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ * @see TabListener
+ * @see TabbedPanel
+ * @see Tab
+ */
+public class TabStateChangedEvent extends TabEvent {
+  private TabbedPanel tabbedPanel;
+  private Tab previousTab;
+  private Tab currentTab;
+
+  /**
+   * Constructs a TabStateChangedEvent
+   *
+   * @param source      the tabbed panel or tab that is the source for this
+   *                    event
+   * @param tabbedPanel the tabbep panel in which the state change occured
+   * @param tab         the tab that is effectd by this event
+   * @param previousTab the tab that was previously in this state
+   * @param currentTab  the tab that is now in this state
+   */
+  public TabStateChangedEvent(Object source, TabbedPanel tabbedPanel, Tab tab, Tab previousTab, Tab currentTab) {
+    super(source, tab);
+    this.tabbedPanel = tabbedPanel;
+    this.previousTab = previousTab;
+    this.currentTab = currentTab;
+  }
+
+  /**
+   * Gets the TabbedPanel in which the state change occured
+   *
+   * @return the TabbedPanel in which the state change occured
+   */
+  public TabbedPanel getTabbedPanel() {
+    return tabbedPanel;
+  }
+
+  /**
+   * Gets the previous Tab
+   *
+   * @return the previous Tab before the change or null if no previous tab was in
+   *         that state before the change
+   */
+  public Tab getPreviousTab() {
+    return previousTab;
+  }
+
+  /**
+   * Gets the current Tab
+   *
+   * @return the current Tab after the change or null if no current tab is in the
+   *         that state after the change
+   */
+  public Tab getCurrentTab() {
+    return currentTab;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabbedPanel.java b/src/net/infonode/tabbedpanel/TabbedPanel.java
new file mode 100644
index 0000000..2118635
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedPanel.java
@@ -0,0 +1,1625 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanel.java,v 1.167 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.gui.*;
+import net.infonode.gui.draggable.*;
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.gui.hover.panel.HoverableShapedPanel;
+import net.infonode.gui.layout.DirectionLayout;
+import net.infonode.gui.panel.BaseContainerUtil;
+import net.infonode.gui.shaped.panel.ShapedPanel;
+import net.infonode.properties.gui.InternalPropertiesUtil;
+import net.infonode.properties.gui.util.ButtonProperties;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.PropertyMap;
+import net.infonode.properties.propertymap.PropertyMapTreeListener;
+import net.infonode.properties.propertymap.PropertyMapWeakListenerManager;
+import net.infonode.tabbedpanel.internal.ShadowPainter;
+import net.infonode.tabbedpanel.internal.TabDropDownList;
+import net.infonode.tabbedpanel.internal.TabbedHoverUtil;
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+import net.infonode.util.Direction;
+import net.infonode.util.ValueChange;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * <p>
+ * A TabbedPanel is a component that handles a group of components in a notebook
+ * like manor. Each component is represented by a {@link Tab}. A tab is a
+ * component itself that defines how the tab will be rendered. The tab also
+ * holds a reference to the content component associated with the tab. The
+ * tabbed panel is divided into two areas, the tab area where the tabs are
+ * displayed and the content area where the tab's content component is
+ * displayed.
+ * </p>
+ *
+ * <p>
+ * The demo program for InfoNode Tabbed Panel on
+ * <a href="http://www.infonode.net/index.html?itpdemo" target="_blank">
+ * www.infonode.net</a> demonstrates and explains most of the tabbed panel's
+ * features.
+ * </p>
+ *
+ * <p>
+ * The tabbed panel is configured using a {@link TabbedPanelProperties} object.
+ * A tabbed panel will always have a properties object with default values based
+ * on the current Look and Feel
+ * </p>
+ *
+ * <p>
+ * Tabs can be added, inserted, removed, selected, highlighted, dragged and
+ * moved.
+ * </p>
+ *
+ * <p>
+ * The tabbed panel support tab placement in a horizontal line above or under
+ * the content area or a vertical row to the left or to the right of the content
+ * area. The tab line can be laid out as either scrolling or compression. If the
+ * tabs are too many to fit in the tab area and scrolling is enabled, then the
+ * mouse wheel is activated and scrollbuttons are shown so that the tabs can be
+ * scrolled. Compression means that the tabs will be downsized to fit into the
+ * visible tab area.
+ * </p>
+ *
+ * <p>
+ * It is possible to display a button in the tab area next to the tabs that shows
+ * a drop down list (called tab drop down list) with all the tabs where it is
+ * possible to select a tab. This is for example useful when the tabbed panel
+ * contains a large amount of tabs or if some tabs are scrolled out. The drop down
+ * list can show a text and an icon for a tab. The text is retrieved by calling
+ * toString() on the tab and the icon is only retrieved if the tab implements the
+ * {@link net.infonode.gui.icon.IconProvider} interface.
+ * </p>
+ *
+ * <p>
+ * It is possible to set an array of components (called tab area components) to
+ * be shown next to the tabs in the tab area, the same place where the drop down
+ * list and the scrollbuttons are shown. This for example useful for adding
+ * buttons to the tabbed panel.
+ * </p>
+ *
+ * <p>
+ * It is possible to add a {@link TabListener} and receive events when a tab is
+ * added, removed, selected, deselected, highlighted, dehighlighted, moved,
+ * dragged, dropped or drag is aborted. The listener will receive events for all
+ * the tabs in the tabbed panel. A tabbed panel will trigger selected,
+ * deselected, highlighted and dehighlighted even if for example the selected
+ * tab is null (no selected tab), i.e. null will be treated as if it was a tab.
+ * </p>
+ *
+ * <p>
+ * A tabbed panel supports several mouse hover alternatives. It is possible to
+ * specify {@link HoverListener}s for the entire tabbed panel, the tab area, the
+ * tab area components area and the content area. The listeners are set in the
+ * TabbedPanelProperties, TabAreaProperties, TabAreaComponentsProperties and the
+ * TabbedPanelContentPanelProperties. A hover listener is called when the mouse
+ * enter or exits the area. The hover listener is called with a {@link HoverEvent}
+ * and the source for the event is always the hovered tabbed panel.
+ * </p>
+ *
+ * <p>
+ * A tabbed panel calls the hover listeners in the following order:
+ * <ol>
+ * <li>The hover listener for the tabbed panel itself.
+ * <li>The hover listener for the tab area or the content area depending on where the
+ * mouse pointer is located.
+ * <li>The hover listener for the tab area components area if the mouse pointer is over
+ * that area.
+ * </ol>
+ * When the tabbed panel is no longer hovered, the hover listenrs are called in the
+ * reverse order.
+ * </p>
+ *
+ * <p>
+ * It is possible to specify different hover policies ({@link TabbedPanelHoverPolicy})
+ * in the TabbedPanelProperties that affects all hover areas of the tabbed panel.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.167 $
+ * @see Tab
+ * @see TitledTab
+ * @see TabbedPanelProperties
+ * @see TabListener
+ * @see TabbedPanelHoverPolicy
+ * @see HoverListener
+ */
+public class TabbedPanel extends JPanel {
+  // Shadow property values
+  private int shadowSize = 4;
+  private ComponentPaintChecker shadowRepaintChecker;
+
+  private TabDropDownList dropDownList;
+
+  private JComponent contentPanel;
+
+  private JComponent[] tabAreaComponents;
+
+  private Direction tabAreaOrientation;
+  private TabDropDownListVisiblePolicy listVisiblePolicy = TabDropDownListVisiblePolicy.NEVER;
+  private TabLayoutPolicy listTabLayoutPolicy = TabLayoutPolicy.SCROLLING;
+  private DraggableComponentBox draggableComponentBox = new DraggableComponentBox(TabbedUIDefaults.getButtonIconSize(),
+                                                                                  false);
+  private ArrayList listeners;
+  private TabbedPanelProperties properties = new TabbedPanelProperties(TabbedPanelProperties.getDefaultProperties());
+  private Tab highlightedTab;
+
+  private boolean settingHighlighted;
+  private boolean mouseEntered = false;
+  private boolean removingSelected = false;
+
+  private TabAreaVisiblePolicy areaVisiblePolicy = TabAreaVisiblePolicy.ALWAYS;
+
+  private class HoverablePanel extends HoverableShapedPanel {
+    public HoverablePanel(LayoutManager l, HoverListener listener) {
+      super(l, listener, TabbedPanel.this);
+    }
+
+    protected void processMouseEvent(MouseEvent event) {
+      super.processMouseEvent(event);
+      doProcessMouseEvent(event);
+    }
+
+    protected void processMouseMotionEvent(MouseEvent event) {
+      super.processMouseMotionEvent(event);
+      doProcessMouseMotionEvent(event);
+    }
+
+    public boolean acceptHover(ArrayList enterableHoverables) {
+      return getHoverListener() == null ? false : TabbedHoverUtil.acceptTabbedPanelHover(properties.getHoverPolicy(),
+                                                                                         enterableHoverables,
+                                                                                         TabbedPanel.this,
+                                                                                         this);
+    }
+  }
+
+  private ShadowPanel componentsPanel = new ShadowPanel();
+
+  private ScrollButtonBox scrollButtonBox;
+
+  private GridBagConstraints constraints = new GridBagConstraints();
+  private GridBagLayout tabAreaLayoutManager = new GridBagLayout() {
+    public void layoutContainer(Container parent) {
+      setTabAreaComponentsButtonsVisible();
+      super.layoutContainer(parent);
+
+      // Overlap if tab area is too narrow to fit both tabAreaComponentsPanel and draggableComponentBox
+      if (tabAreaComponentsPanel.isVisible()) {
+        if (tabAreaOrientation.isHorizontal()) {
+          if (tabAreaContainer.getHeight() < tabAreaComponentsPanel.getPreferredSize().getHeight()) {
+            draggableComponentBox.setSize(draggableComponentBox.getWidth(), 0);
+            tabAreaComponentsPanel.setSize(tabAreaComponentsPanel.getWidth(), tabAreaContainer.getHeight());
+          }
+        }
+        else {
+          if (tabAreaContainer.getWidth() < tabAreaComponentsPanel.getPreferredSize().getWidth()) {
+            draggableComponentBox.setSize(0, draggableComponentBox.getHeight());
+            tabAreaComponentsPanel.setSize(tabAreaContainer.getWidth(), tabAreaComponentsPanel.getHeight());
+          }
+        }
+      }
+      
+      /*if (contentPanel != null) {
+        int newSize = (tabAreaOrientation == Direction.UP || tabAreaOrientation == Direction.DOWN) ?
+                      draggableComponentBox.getWidth() : draggableComponentBox.getHeight();
+        int newOuterSize = (tabAreaOrientation == Direction.UP || tabAreaOrientation == Direction.DOWN) ?
+                           tabAreaContainer.getWidth() : tabAreaContainer.getHeight();
+        if (newOuterSize == outerSize && newSize != size) {
+          size = newSize;
+          SwingUtilities.invokeLater(new Runnable() {
+            public void run() {
+            	if (contentPanel.isShowing()) {
+            		contentPanel.repaint();
+            	}
+            }
+          });
+        }
+        outerSize = newOuterSize;
+      }*/
+
+      updateShadow();
+    }
+  };
+
+  private HoverableShapedPanel tabAreaContainer = new HoverablePanel(tabAreaLayoutManager,
+                                                                     properties.getTabAreaProperties()
+                                                                     .getHoverListener()) {
+    public Dimension getPreferredSize() {
+      Dimension d = super.getPreferredSize();
+
+      if (getTabCount() == 0) {
+        Insets insets = getInsets();
+        Dimension d2 = tabAreaComponentsPanel.getPreferredSize();
+        d = new Dimension(insets.left + insets.right + d2.width, insets.top + insets.bottom + d2.height);
+      }
+
+      return d;
+    }
+  };
+
+  private HoverableShapedPanel tabAreaComponentsPanel = new HoverablePanel(new DirectionLayout(),
+                                                                           properties.getTabAreaComponentsProperties()
+                                                                           .getHoverListener()) {
+    public Dimension getMaximumSize() {
+      return getPreferredSize();
+    }
+
+    public Dimension getMinimumSize() {
+      return getPreferredSize();
+    }
+
+    public Dimension getPreferredSize() {
+      Dimension d = super.getPreferredSize();
+      Insets insets = getInsets();
+
+      if (ComponentUtil.hasVisibleChildren(this)) {
+        if (tabAreaOrientation.isHorizontal()) {
+          int maxWidth = ComponentUtil.getPreferredMaxWidth(getComponents()) + insets.left + insets.right;
+          return new Dimension(maxWidth, d.height);
+        }
+        else {
+          int maxHeight = ComponentUtil.getPreferredMaxHeight(getComponents()) + insets.top + insets.bottom;
+          return new Dimension(d.width, maxHeight);
+        }
+      }
+
+      return new Dimension(0, 0);
+    }
+  };
+
+  private DraggableComponentBoxListener draggableComponentBoxListener = new DraggableComponentBoxListener() {
+    private boolean selectedMoved;
+
+    public void componentSelected(DraggableComponentBoxEvent event) {
+      if (event.getDraggableComponent() == event.getOldDraggableComponent()) {
+        if (!selectedMoved && properties.getTabDeselectable())
+          draggableComponentBox.selectDraggableComponent(null);
+      }
+      else {
+        Tab tab = findTab(event.getDraggableComponent());
+        setHighlightedTab(tab);
+        Tab oldTab = findTab(event.getOldDraggableComponent());
+        fireSelectedEvent(tab, oldTab);
+        if (removingSelected) {
+          removingSelected = false;
+          if (oldTab != null)
+            oldTab.setTabbedPanel(null);
+        }
+      }
+
+      tabAreaContainer.repaint();
+    }
+
+    public void componentRemoved(DraggableComponentBoxEvent event) {
+      Tab tab = findTab(event.getDraggableComponent());
+      if (highlightedTab == tab)
+        highlightedTab = null;
+
+      setTabAreaComponentsButtonsVisible();
+      updateTabAreaVisibility();
+      //revalidate();
+      tabAreaContainer.repaint();
+      fireRemovedEvent(tab);
+    }
+
+    public void componentAdded(DraggableComponentBoxEvent event) {
+      updateTabAreaVisibility();
+      //revalidate();
+      tabAreaContainer.repaint();
+      fireAddedEvent(findTab(event.getDraggableComponent()));
+    }
+
+    public void componentDragged(DraggableComponentBoxEvent event) {
+      fireDraggedEvent(findTab(event.getDraggableComponent()),
+                       event.getDraggableComponentEvent().getMouseEvent());
+    }
+
+    public void componentDropped(DraggableComponentBoxEvent event) {
+      if (!draggableComponentBox.contains(event.getDraggableComponentBoxPoint()))
+        setHighlightedTab(findTab(draggableComponentBox.getSelectedDraggableComponent()));
+      fireDroppedEvent(findTab(event.getDraggableComponent()),
+                       event.getDraggableComponentEvent().getMouseEvent());
+    }
+
+    public void componentDragAborted(DraggableComponentBoxEvent event) {
+      fireNotDroppedEvent(findTab(event.getDraggableComponent()));
+    }
+
+    public void changed(DraggableComponentBoxEvent event) {
+      if (event.getDraggableComponentEvent() != null) {
+        int type = event.getDraggableComponentEvent().getType();
+
+        if (type == DraggableComponentEvent.TYPE_PRESSED && properties.getHighlightPressedTab()) {
+          if (highlightedTab != null)
+            setHighlightedTab(findTab(event.getDraggableComponent()));
+        }
+        else if (type == DraggableComponentEvent.TYPE_RELEASED) {
+          selectedMoved = false;
+          setHighlightedTab(getSelectedTab());
+        }
+        else if (type == DraggableComponentEvent.TYPE_DISABLED && highlightedTab != null &&
+                 highlightedTab.getDraggableComponent() == event.getDraggableComponent())
+          setHighlightedTab(null);
+        else if (type == DraggableComponentEvent.TYPE_ENABLED &&
+                 draggableComponentBox.getSelectedDraggableComponent() == event.getDraggableComponent())
+          setHighlightedTab(findTab(event.getDraggableComponent()));
+        else if (type == DraggableComponentEvent.TYPE_MOVED) {
+          tabAreaContainer.repaint();
+          selectedMoved = event.getDraggableComponent() == draggableComponentBox.getSelectedDraggableComponent();
+          fireTabMoved(findTab(event.getDraggableComponent()));
+        }
+      }
+      else {
+        // Scrolling
+        tabAreaContainer.repaint();
+      }
+
+      updateShadow();
+    }
+  };
+
+  private PropertyMapTreeListener propertyChangedListener = new PropertyMapTreeListener() {
+    public void propertyValuesChanged(Map changes) {
+      updateProperties(changes);
+      updatePropertiesForTabArea(changes);
+      updatePropertiesForTabAreaComponentsArea(changes);
+      updatePropertiesForTabAreaComponentsButtons(changes);
+
+      updateScrollButtons();
+      checkIfOnlyOneTab(true);
+    }
+  };
+
+  private void updatePropertiesForTabAreaComponentsButtons(Map changes) {
+    TabbedPanelButtonProperties buttonProps = properties.getButtonProperties();
+    Map m = (Map) changes.get(buttonProps.getTabDropDownListButtonProperties().getMap());
+    {
+      if (m != null) {
+        if (dropDownList != null) {
+          AbstractButton b = dropDownList.getButton();
+          if (m.keySet().contains(ButtonProperties.FACTORY)) {
+            b =
+            properties.getButtonProperties().getTabDropDownListButtonProperties().getFactory().createButton(
+                TabbedPanel.this);
+            dropDownList.setButton(b);
+          }
+
+          properties.getButtonProperties().getTabDropDownListButtonProperties().applyTo(b);
+        }
+      }
+    }
+
+    if (scrollButtonBox != null) {
+      AbstractButton buttons[] = new AbstractButton[]{scrollButtonBox.getUpButton(),
+                                                      scrollButtonBox.getDownButton(),
+                                                      scrollButtonBox.getLeftButton(),
+                                                      scrollButtonBox.getRightButton()
+      };
+
+      ButtonProperties props[] = new ButtonProperties[]{buttonProps.getScrollUpButtonProperties(),
+                                                        buttonProps.getScrollDownButtonProperties(),
+                                                        buttonProps.getScrollLeftButtonProperties(),
+                                                        buttonProps.getScrollRightButtonProperties()
+      };
+
+      for (int i = 0; i < buttons.length; i++) {
+        m = (Map) changes.get(props[i].getMap());
+        if (m != null) {
+          if (m.keySet().contains(ButtonProperties.FACTORY))
+            buttons[i] = props[i].getFactory().createButton(TabbedPanel.this);
+
+          props[i].applyTo(buttons[i]);
+        }
+      }
+
+      scrollButtonBox.setButtons(buttons[0], buttons[1], buttons[2], buttons[3]);
+    }
+  }
+
+  private void updateAllDefaultValues() {
+    // General
+    updateAllTabsProperties();
+    draggableComponentBox.setScrollEnabled(properties.getTabLayoutPolicy() == TabLayoutPolicy.SCROLLING);
+    updateTabDropDownList();
+    draggableComponentBox.setScrollOffset(properties.getTabScrollingOffset());
+    draggableComponentBox.setEnsureSelectedVisible(properties.getEnsureSelectedTabVisible());
+
+    tabAreaOrientation = properties.getTabAreaOrientation();
+    updatePropertiesForTabAreaLayoutConstraints();
+    componentsPanel.add(tabAreaContainer, ComponentUtil.getBorderLayoutOrientation(tabAreaOrientation));
+    componentsPanel.revalidate();
+
+    draggableComponentBox.setComponentSpacing(properties.getTabSpacing());
+    draggableComponentBox.setDepthSortOrder(properties.getTabDepthOrderPolicy() == TabDepthOrderPolicy.DESCENDING);
+    draggableComponentBox.setAutoSelect(properties.getAutoSelectTab());
+
+    shadowSize = properties.getShadowSize();
+    componentsPanel.setBorder(
+        contentPanel != null && properties.getShadowEnabled() ? new EmptyBorder(0, 0, shadowSize, shadowSize) : null);
+
+    componentsPanel.setHoverListener(properties.getHoverListener());
+
+    // Tab area
+    tabAreaContainer.setHoverListener(properties.getTabAreaProperties().getHoverListener());
+    ShapedPanelProperties shapedProps = properties.getTabAreaProperties().getShapedPanelProperties();
+    properties.getTabAreaProperties().getComponentProperties().applyTo(tabAreaContainer);
+    updateIntelligentInsets(tabAreaContainer, properties.getTabAreaProperties().getComponentProperties());
+    updateShapedPanelProperties(tabAreaContainer, properties.getTabAreaProperties().getShapedPanelProperties());
+
+
+    // Tab area components area
+    tabAreaComponentsPanel.setHoverListener(properties.getTabAreaComponentsProperties().getHoverListener());
+    updatePropertiesForTabAreaLayoutConstraints();
+    shapedProps = properties.getTabAreaComponentsProperties().getShapedPanelProperties();
+    properties.getTabAreaComponentsProperties().getComponentProperties().applyTo(tabAreaComponentsPanel);
+    updateIntelligentInsets(tabAreaComponentsPanel,
+                            properties.getTabAreaComponentsProperties().getComponentProperties());
+    updateShapedPanelProperties(tabAreaComponentsPanel,
+                                shapedProps);
+    tabAreaComponentsPanel.setHorizontalFlip(tabAreaOrientation == Direction.DOWN || tabAreaOrientation == Direction.LEFT ?
+                                             !shapedProps.getHorizontalFlip() : shapedProps.getHorizontalFlip());
+    tabAreaComponentsPanel.setVerticalFlip(shapedProps.getVerticalFlip());
+
+    updatePanelOpaque();
+  }
+
+
+  private void updateProperties(Map changes) {
+    Map m = getMap(changes, properties.getMap());
+    if (m != null) {
+      Set keySet = m.keySet();
+
+      // Properties contained by tabs
+      if (keySet.contains(TabbedPanelProperties.TAB_REORDER_ENABLED) || m.keySet().contains(
+          TabbedPanelProperties.ABORT_DRAG_KEY)
+          || m.keySet().contains(TabbedPanelProperties.TAB_SELECT_TRIGGER))
+        updateAllTabsProperties();
+
+      // Other
+      if (keySet.contains(TabbedPanelProperties.TAB_LAYOUT_POLICY) && getTabCount() > 1)
+        draggableComponentBox.setScrollEnabled(
+            ((TabLayoutPolicy) ((ValueChange) m.get(TabbedPanelProperties.TAB_LAYOUT_POLICY)).getNewValue()) == TabLayoutPolicy.SCROLLING);
+
+      if (keySet.contains(TabbedPanelProperties.TAB_DROP_DOWN_LIST_VISIBLE_POLICY))
+        updateTabDropDownList();
+
+      if (keySet.contains(TabbedPanelProperties.TAB_SCROLLING_OFFSET))
+        draggableComponentBox.setScrollOffset(
+            ((Integer) ((ValueChange) m.get(TabbedPanelProperties.TAB_SCROLLING_OFFSET)).getNewValue()).intValue());
+
+      if (keySet.contains(TabbedPanelProperties.ENSURE_SELECTED_VISIBLE))
+        draggableComponentBox.setEnsureSelectedVisible(
+            ((Boolean) ((ValueChange) m.get(TabbedPanelProperties.ENSURE_SELECTED_VISIBLE)).getNewValue()).booleanValue());
+
+      if (keySet.contains(TabbedPanelProperties.TAB_AREA_ORIENTATION)) {
+        tabAreaOrientation = (Direction) ((ValueChange) m.get(TabbedPanelProperties.TAB_AREA_ORIENTATION)).getNewValue();
+        updatePropertiesForTabAreaLayoutConstraints();
+        componentsPanel.remove(tabAreaContainer);
+        componentsPanel.add(tabAreaContainer, ComponentUtil.getBorderLayoutOrientation(tabAreaOrientation));
+
+        componentsPanel.revalidate();
+
+        properties.getTabAreaComponentsProperties().getComponentProperties().applyTo(tabAreaComponentsPanel);
+        updateIntelligentInsets(tabAreaContainer, properties.getTabAreaProperties().getComponentProperties());
+        tabAreaComponentsPanel.setDirection(tabAreaOrientation.getNextCW());
+        updateIntelligentInsets(tabAreaComponentsPanel,
+                                properties.getTabAreaComponentsProperties().getComponentProperties());
+      }
+
+      if (keySet.contains(TabbedPanelProperties.TAB_SPACING))
+        draggableComponentBox.setComponentSpacing(
+            ((Integer) ((ValueChange) m.get(TabbedPanelProperties.TAB_SPACING)).getNewValue()).intValue());
+
+      if (keySet.contains(TabbedPanelProperties.TAB_DEPTH_ORDER))
+        draggableComponentBox.setDepthSortOrder(
+            ((TabDepthOrderPolicy) ((ValueChange) m.get(TabbedPanelProperties.TAB_DEPTH_ORDER)).getNewValue()) == TabDepthOrderPolicy.DESCENDING);
+
+      if (keySet.contains(TabbedPanelProperties.AUTO_SELECT_TAB))
+        draggableComponentBox.setAutoSelect(
+            ((Boolean) ((ValueChange) m.get(TabbedPanelProperties.AUTO_SELECT_TAB)).getNewValue()).booleanValue());
+
+      /*
+       * if
+       * (keySet.contains(TabbedPanelProperties.HIGHLIGHT_PRESSED_TAB)) { //
+       * Elsewhere }
+       *
+       * if (keySet.contains(TabbedPanelProperties.TAB_DESELECTABLE)) { //
+       * Elsewhere }
+       */
+
+      if (keySet.contains(TabbedPanelProperties.SHADOW_ENABLED) || m.keySet().contains(
+          TabbedPanelProperties.SHADOW_STRENGTH) || m.keySet().contains(TabbedPanelProperties.SHADOW_COLOR) || m.keySet()
+          .contains(TabbedPanelProperties.SHADOW_BLEND_AREA_SIZE) || m.keySet().contains(
+              TabbedPanelProperties.SHADOW_SIZE) || m.keySet().contains(TabbedPanelProperties.PAINT_TAB_AREA_SHADOW)) {
+        shadowSize = properties.getShadowSize();
+        componentsPanel.setBorder(
+            contentPanel != null && properties.getShadowEnabled() ?
+            new EmptyBorder(0, 0, shadowSize, shadowSize) : null);
+      }
+
+      if (keySet.contains(TabbedPanelProperties.HOVER_LISTENER)) {
+        componentsPanel.setHoverListener(
+            (HoverListener) ((ValueChange) m.get(TabbedPanelProperties.HOVER_LISTENER)).getNewValue());
+      }
+    }
+
+    updatePanelOpaque();
+  }
+
+  private void updatePropertiesForTabArea(Map changes) {
+    Map m = getMap(changes, properties.getTabAreaProperties().getMap());
+    if (m != null) {
+      if (m.keySet().contains(TabAreaProperties.HOVER_LISTENER)) {
+        tabAreaContainer.setHoverListener(
+            (HoverListener) ((ValueChange) m.get(TabAreaProperties.HOVER_LISTENER)).getNewValue());
+      }
+
+      areaVisiblePolicy = getProperties().getTabAreaProperties().getTabAreaVisiblePolicy();
+      updateTabAreaVisibility();
+    }
+
+    m = getMap(changes, properties.getTabAreaProperties().getComponentProperties().getMap());
+    Map m2 = getMap(changes, properties.getTabAreaProperties().getShapedPanelProperties().getMap());
+    if (m != null || m2 != null) {
+      properties.getTabAreaProperties().getComponentProperties().applyTo(tabAreaContainer);
+      updateIntelligentInsets(tabAreaContainer, properties.getTabAreaProperties().getComponentProperties());
+
+      updateShapedPanelProperties(tabAreaContainer, properties.getTabAreaProperties().getShapedPanelProperties());
+
+      repaint();
+    }
+  }
+
+  private void updateIntelligentInsets(JComponent c, ComponentProperties props) {
+    Direction d = properties.getTabAreaOrientation();
+    Insets insets = props.getInsets();
+    if (insets != null) {
+      if (d == Direction.RIGHT)
+        insets = new Insets(insets.left, insets.bottom, insets.right, insets.top);
+      else if (d == Direction.DOWN)
+        insets = new Insets(insets.bottom, insets.left, insets.top, insets.right);
+      else if (d == Direction.LEFT)
+        insets = new Insets(insets.left, insets.top, insets.right, insets.bottom);
+
+      Border b = props.getBorder();
+      c.setBorder(b != null ? (Border) new CompoundBorder(b, new EmptyBorder(insets)) : (Border) new EmptyBorder(
+          insets));
+    }
+  }
+
+  private void updatePropertiesForTabAreaComponentsArea(Map changes) {
+    Map m = getMap(changes, properties.getTabAreaComponentsProperties().getMap());
+
+    if (m != null) {
+      if (m.keySet().contains(TabAreaComponentsProperties.HOVER_LISTENER)) {
+        tabAreaComponentsPanel.setHoverListener(
+            (HoverListener) ((ValueChange) m.get(TabAreaComponentsProperties.HOVER_LISTENER)).getNewValue());
+      }
+
+      if (m.keySet().contains(TabAreaComponentsProperties.STRETCH_ENABLED)) {
+        updatePropertiesForTabAreaLayoutConstraints();
+      }
+    }
+
+    m = getMap(changes, properties.getTabAreaComponentsProperties().getComponentProperties().getMap());
+    Map m2 = getMap(changes, properties.getTabAreaComponentsProperties().getShapedPanelProperties().getMap());
+    if (m != null || m2 != null) {
+      ShapedPanelProperties shapedProps = properties.getTabAreaComponentsProperties().getShapedPanelProperties();
+      properties.getTabAreaComponentsProperties().getComponentProperties().applyTo(tabAreaComponentsPanel);
+      updateIntelligentInsets(tabAreaComponentsPanel,
+                              properties.getTabAreaComponentsProperties().getComponentProperties());
+      updateShapedPanelProperties(tabAreaComponentsPanel, shapedProps);
+      tabAreaComponentsPanel.setHorizontalFlip(
+          tabAreaOrientation == Direction.DOWN || tabAreaOrientation == Direction.LEFT ?
+          !shapedProps.getHorizontalFlip() : shapedProps.getHorizontalFlip());
+      tabAreaComponentsPanel.setVerticalFlip(shapedProps.getVerticalFlip());
+    }
+  }
+
+  private void updatePropertiesForTabAreaLayoutConstraints() {
+    boolean stretch = properties.getTabAreaComponentsProperties().getStretchEnabled();
+    if (tabAreaOrientation == Direction.UP) {
+      setTabAreaLayoutConstraints(draggableComponentBox,
+                                  0,
+                                  0,
+                                  GridBagConstraints.HORIZONTAL,
+                                  1,
+                                  1,
+                                  GridBagConstraints.SOUTH);
+      setTabAreaLayoutConstraints(tabAreaComponentsPanel,
+                                  1,
+                                  0,
+                                  stretch ? GridBagConstraints.VERTICAL : GridBagConstraints.NONE,
+                                  0,
+                                  1,
+                                  GridBagConstraints.SOUTH);
+      updateTabAreaComponentsPanel(Direction.RIGHT, 0, 1);
+    }
+    else if (tabAreaOrientation == Direction.DOWN) {
+      setTabAreaLayoutConstraints(draggableComponentBox,
+                                  0,
+                                  0,
+                                  GridBagConstraints.HORIZONTAL,
+                                  1,
+                                  1,
+                                  GridBagConstraints.NORTH);
+      setTabAreaLayoutConstraints(tabAreaComponentsPanel,
+                                  1,
+                                  0,
+                                  stretch ? GridBagConstraints.VERTICAL : GridBagConstraints.NONE,
+                                  0,
+                                  0,
+                                  GridBagConstraints.NORTH);
+      updateTabAreaComponentsPanel(Direction.RIGHT, 0, 0);
+    }
+    else if (tabAreaOrientation == Direction.LEFT) {
+      setTabAreaLayoutConstraints(draggableComponentBox,
+                                  0,
+                                  0,
+                                  GridBagConstraints.VERTICAL,
+                                  1,
+                                  1,
+                                  GridBagConstraints.EAST);
+      setTabAreaLayoutConstraints(tabAreaComponentsPanel,
+                                  0,
+                                  1,
+                                  stretch ? GridBagConstraints.HORIZONTAL : GridBagConstraints.NONE,
+                                  0,
+                                  0,
+                                  GridBagConstraints.EAST);
+      updateTabAreaComponentsPanel(Direction.DOWN, 0, 0);
+    }
+    else {
+      setTabAreaLayoutConstraints(draggableComponentBox,
+                                  0,
+                                  0,
+                                  GridBagConstraints.VERTICAL,
+                                  1,
+                                  1,
+                                  GridBagConstraints.WEST);
+      setTabAreaLayoutConstraints(tabAreaComponentsPanel,
+                                  0,
+                                  1,
+                                  stretch ? GridBagConstraints.HORIZONTAL : GridBagConstraints.NONE,
+                                  0,
+                                  0,
+                                  GridBagConstraints.WEST);
+      updateTabAreaComponentsPanel(Direction.DOWN, 0, 1);
+    }
+
+    draggableComponentBox.setComponentDirection(tabAreaOrientation);
+  }
+
+  private Map getMap(Map changes, PropertyMap map) {
+    return changes != null ? (Map) changes.get(map) : null;
+  }
+
+  private void updateTabAreaVisibility() {
+    if (areaVisiblePolicy == TabAreaVisiblePolicy.ALWAYS)
+      tabAreaContainer.setVisible(true);
+    else if (areaVisiblePolicy == TabAreaVisiblePolicy.NEVER)
+      tabAreaContainer.setVisible(false);
+    else if (areaVisiblePolicy == TabAreaVisiblePolicy.MORE_THAN_ONE_TAB)
+      tabAreaContainer.setVisible(getTabCount() > 1);
+    else if (areaVisiblePolicy == TabAreaVisiblePolicy.TABS_EXIST)
+      tabAreaContainer.setVisible(getTabCount() > 0);
+
+    if (!tabAreaContainer.isVisible())
+      tabAreaContainer.setSize(0, 0);
+  }
+
+  /**
+   * Constructs a TabbedPanel with a TabbedPanelContentPanel as content area
+   * component and with default TabbedPanelProperties
+   *
+   * @see TabbedPanelProperties
+   * @see TabbedPanelContentPanel
+   */
+  public TabbedPanel() {
+    initialize(new TabbedPanelContentPanel(this, new TabContentPanel(this)));
+  }
+
+  /**
+   * <p>
+   * Constructs a TabbedPanel with a custom component as content area
+   * component or without any content area component and with default
+   * TabbedPanelProperties. The properties for the content area will not be used.
+   * </p>
+   *
+   * <p>
+   * If no content area component is used, then the tabbed panel will act as a
+   * bar and the tabs will be laid out in a line.
+   * </p>
+   *
+   * <p>
+   * <strong>Note: </strong> A custom content area component is by itself
+   * responsible for showing a tab's content component when a tab is selected,
+   * for eaxmple by listening to events from the tabbed panel. The component
+   * will be laid out just as the default content area component so that
+   * shadows etc. can be used.
+   * </p>
+   *
+   * @param contentAreaComponent component to be used as content area component or null for no
+   *                             content area component
+   * @see TabbedPanelProperties
+   */
+  public TabbedPanel(JComponent contentAreaComponent) {
+    this(contentAreaComponent, false);
+  }
+
+  /**
+   * <p>
+   * Constructs a TabbedPanel with a custom component as content area
+   * component or without any content area component and with default
+   * TabbedPanelProperties. It's possible to choose if the properties for the
+   * content area should be used or not.
+   * </p>
+   *
+   * <p>
+   * If no content area component is used, then the tabbed panel will act as a
+   * bar and the tabs will be laid out in a line.
+   * </p>
+   *
+   * <p>
+   * <strong>Note: </strong> A custom content area component is by itself
+   * responsible for showing a tab's content component when a tab is selected,
+   * for eaxmple by listening to events from the tabbed panel. The component
+   * will be laid out just as the default content area component so that
+   * shadows etc. can be used.
+   * </p>
+   *
+   * @param contentAreaComponent component to be used as content area component or null for no
+   *                             content area component
+   * @param useProperties        true if the properties for the content area should be used,
+   *                             otherwise false
+   * @see TabbedPanelProperties
+   * @since ITP 1.4.0
+   */
+  public TabbedPanel(JComponent contentAreaComponent, boolean useProperties) {
+    if (useProperties)
+      initialize(new TabbedPanelContentPanel(this, contentAreaComponent));
+    else
+      initialize(contentAreaComponent);
+  }
+
+  /**
+   * Check if the tab area contains the given point
+   *
+   * @param p the point to check. Must be relative to this tabbed panel.
+   * @return true if tab area contains point, otherwise false
+   * @see #contentAreaContainsPoint
+   */
+  public boolean tabAreaContainsPoint(Point p) {
+    if (!tabAreaContainer.isVisible())
+      return false;
+
+    return tabAreaContainer.contains(SwingUtilities.convertPoint(this, p, tabAreaContainer));
+  }
+
+  /**
+   * Check if the content area contains the given point
+   *
+   * @param p the point to check. Must be relative to this tabbed panel.
+   * @return true if content area contains point, otherwise false
+   * @see #tabAreaContainsPoint
+   */
+  public boolean contentAreaContainsPoint(Point p) {
+    return contentPanel != null ? contentPanel.contains(SwingUtilities.convertPoint(this, p, contentPanel)) : false;
+  }
+
+  /**
+   * Checks if the tab area is currently visible
+   *
+   * @return true if visible, otherwise false
+   * @since ITP 1.4.0
+   */
+  public boolean isTabAreaVisible() {
+    return tabAreaContainer.isVisible();
+  }
+
+  /**
+   * <p>
+   * Add a tab. The tab will be added after the last tab.
+   * </p>
+   *
+   * <p>
+   * If the tab to be added is the only tab in this tabbed panel and the
+   * property "Auto Select Tab" is enabled then the tab will become selected
+   * in this tabbed panel after the tab has been added.
+   * </p>
+   *
+   * @param tab tab to be added
+   * @see #insertTab(Tab, int)
+   * @see TabbedPanelProperties
+   */
+  public void addTab(Tab tab) {
+    doInsertTab(tab, null, -1);
+  }
+
+  /**
+   * <p>
+   * Insert a tab at the specified tab index (position).
+   * </p>
+   *
+   * <p>
+   * If the tab to be inserted is the only tab in this tabbed panel and the
+   * property "Auto Select Tab" is enabled then the tab will become selected
+   * in this tabbed panel after the tab has been inserted.
+   * </p>
+   *
+   * @param tab   tab to be inserted
+   * @param index the index to insert tab at
+   * @see #addTab
+   * @see TabbedPanelProperties
+   */
+  public void insertTab(Tab tab, int index) {
+    doInsertTab(tab, null, index);
+  }
+
+  /**
+   * <p>
+   * Insert a tab at the specified point.
+   * </p>
+   *
+   * <p>
+   * If the point is outside the tab area then the tab will be inserted after
+   * the last tab.
+   * </p>
+   *
+   * <p>
+   * If the tab to be inserted is the only tab in this tabbed panel and the
+   * property "Auto Select Tab" is enabled then the tab will become selected
+   * in this tabbed panel after the tab has been inserted.
+   * </p>
+   *
+   * @param tab tab to be inserted
+   * @param p   the point to insert tab at. Must be relative to this tabbed
+   *            panel.
+   * @see #addTab
+   * @see TabbedPanelProperties
+   */
+  public void insertTab(Tab tab, Point p) {
+    doInsertTab(tab, p, -1);
+  }
+
+  /**
+   * Removes a tab
+   *
+   * @param tab tab to be removed from this TabbedPanel
+   */
+  public void removeTab(Tab tab) {
+    if (tab != null && tab.getTabbedPanel() == this) {
+      if (getSelectedTab() != tab) {
+        tab.setTabbedPanel(null);
+      }
+      else {
+        removingSelected = true;
+      }
+      draggableComponentBox.removeDraggableComponent(tab.getDraggableComponent());
+    }
+    checkIfOnlyOneTab(false);
+  }
+
+  /**
+   * Move tab to point p. If p is outside the tab area then the tab is not
+   * moved.
+   *
+   * @param tab tab to move. Tab must be a member (added/inserted) of this
+   *            tabbed panel.
+   * @param p   point to move tab to. Must be relative to this tabbed panel.
+   */
+  public void moveTab(Tab tab, Point p) {
+    draggableComponentBox.dragDraggableComponent(tab.getDraggableComponent(),
+                                                 SwingUtilities.convertPoint(this, p, draggableComponentBox));
+  }
+
+  /**
+   * Selects a tab, i.e. displays the tab's content component in this tabbed
+   * panel's content area
+   *
+   * @param tab tab to select. Tab must be a member (added/inserted) of this
+   *            tabbed panel.
+   */
+  public void setSelectedTab(Tab tab) {
+    if (getSelectedTab() == tab)
+      return;
+
+    if (tab != null) {
+      if (tab.isEnabled() && getTabIndex(tab) > -1) {
+        if (tab.getDraggableComponent() == draggableComponentBox.getSelectedDraggableComponent()) {
+          setHighlightedTab(tab);
+        }
+        else {
+          tab.setSelected(true);
+        }
+      }
+    }
+    else {
+      draggableComponentBox.selectDraggableComponent(null);
+    }
+  }
+
+  /**
+   * Gets the selected tab, i.e. the tab who's content component is currently
+   * displayed in this tabbed panel's content area
+   *
+   * @return the selected tab or null if no tab is selected in this tabbed
+   *         panel
+   */
+  public Tab getSelectedTab() {
+    return findTab(draggableComponentBox.getSelectedDraggableComponent());
+  }
+
+  /**
+   * Sets which tab that should be highlighted, i.e. signal highlighted state
+   * to the tab
+   *
+   * @param highlightedTab tab that should be highlighted or null if no tab should be
+   *                       highlighted. The tab must be a member (added/inserted) of this
+   *                       tabbed panel.
+   */
+  public void setHighlightedTab(Tab highlightedTab) {
+    if (!settingHighlighted) {
+      settingHighlighted = true;
+      Tab oldTab = this.highlightedTab;
+      Tab newTab = null;
+      if (oldTab != highlightedTab)
+        draggableComponentBox.setTopComponent(highlightedTab != null ? highlightedTab.getDraggableComponent() : null);
+      if (highlightedTab != null) {
+        if (getTabIndex(highlightedTab) > -1) {
+          this.highlightedTab = highlightedTab;
+          if (oldTab != null && oldTab != highlightedTab) {
+            oldTab.setHighlighted(false);
+          }
+
+          if (oldTab != highlightedTab)
+            if (highlightedTab.isEnabled()) {
+              highlightedTab.setHighlighted(true);
+            }
+            else {
+              highlightedTab.setHighlighted(false);
+              this.highlightedTab = null;
+            }
+
+          if (highlightedTab.isEnabled() && highlightedTab != oldTab)
+            newTab = highlightedTab;
+
+          if (oldTab != highlightedTab)
+            fireHighlightedEvent(newTab, oldTab);
+        }
+      }
+      else if (oldTab != null) {
+        this.highlightedTab = null;
+        oldTab.setHighlighted(false);
+        fireHighlightedEvent(null, oldTab);
+      }
+
+      updateShadow();
+      settingHighlighted = false;
+    }
+  }
+
+  /**
+   * Gets the highlighted tab
+   *
+   * @return the highlighted tab or null if no tab is highlighted in this
+   *         tabbed panel
+   */
+  public Tab getHighlightedTab() {
+    return highlightedTab;
+  }
+
+  /**
+   * Gets the number of tabs
+   *
+   * @return number of tabs
+   */
+  public int getTabCount() {
+    return draggableComponentBox.getDraggableComponentCount();
+  }
+
+  /**
+   * Gets the tab at index
+   *
+   * @param index index of tab
+   * @return tab at index
+   * @throws ArrayIndexOutOfBoundsException if there is no tab at index
+   */
+  public Tab getTabAt(int index) {
+    DraggableComponent component = draggableComponentBox.getDraggableComponentAt(index);
+    return component == null ? null : (Tab) component.getComponent();
+  }
+
+  /**
+   * Gets the index for tab
+   *
+   * @param tab tab
+   * @return index or -1 if tab is not a member of this TabbedPanel
+   */
+  public int getTabIndex(Tab tab) {
+    return tab == null ? -1 : draggableComponentBox.getDraggableComponentIndex(tab.getDraggableComponent());
+  }
+
+  /**
+   * <p>
+   * Scrolls the given tab into the visible area of the tab area.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> This only has effect if the active tab layout
+   * policy is scrolling.
+   * </p>
+   *
+   * @param tab tab to scroll into visible tab area
+   * @since ITP 1.4.0
+   */
+  public void scrollTabToVisibleArea(Tab tab) {
+    if (tab.getTabbedPanel() == this)
+      draggableComponentBox.scrollToVisible(tab.getDraggableComponent());
+  }
+
+  /**
+   * Sets an array of components that will be shown in the tab area next to
+   * the tabs, i.e. to the right or below the tabs depending on the tab area
+   * orientation.
+   *
+   * The components will be laid out in a line and the direction
+   * will change depending on the tab area orientation. Tab drop down list and
+   * scroll buttons are also tab area components but those are handled
+   * automatically by the tabbed panel and are not affected by calling this
+   * method.
+   *
+   * @param tabAreaComponents array of components, null for no components
+   * @since ITP 1.1.0
+   */
+  public void setTabAreaComponents(JComponent[] tabAreaComponents) {
+    if (this.tabAreaComponents != null) {
+      for (int i = 0; i < this.tabAreaComponents.length; i++)
+        tabAreaComponentsPanel.remove(this.tabAreaComponents[i]);
+    }
+
+    this.tabAreaComponents = tabAreaComponents == null ? null : (JComponent[]) tabAreaComponents.clone();
+
+    if (tabAreaComponents != null)
+      for (int i = 0; i < tabAreaComponents.length; i++)
+        tabAreaComponentsPanel.add(tabAreaComponents[i]);
+
+    setTabAreaComponentsButtonsVisible();
+    tabAreaComponentsPanel.revalidate();
+  }
+
+  /**
+   * Gets if any tab area components i.e. scroll buttons etc are visible at the moment
+   *
+   * @return true if visible, otherwise false
+   * @since ITP 1.2.0
+   */
+  public boolean isTabAreaComponentsVisible() {
+    return tabAreaComponentsPanel.isVisible();
+  }
+
+  /**
+   * Gets the tab area components.
+   *
+   * Tab drop down list and scroll buttons are also tab area components but
+   * those are handled automatically by the tabbed panel and no references
+   * to them will be returned. This method only returns the components that
+   * have been set with the setTabAreaComponents method.
+   *
+   * @return an array of tab area components or null if none
+   * @see #setTabAreaComponents
+   * @since ITP 1.1.0
+   */
+  public JComponent[] getTabAreaComponents() {
+    return tabAreaComponents == null ? null : (JComponent[]) tabAreaComponents.clone();
+  }
+
+  /**
+   * Adds a TablListener that will receive events for all the tabs in this
+   * TabbedPanel
+   *
+   * @param listener the TabListener to add
+   */
+  public void addTabListener(TabListener listener) {
+    if (listeners == null)
+      listeners = new ArrayList(2);
+
+    listeners.add(listener);
+  }
+
+  /**
+   * Removes a TabListener
+   *
+   * @param listener the TabListener to remove
+   */
+  public void removeTabListener(TabListener listener) {
+    if (listeners != null) {
+      listeners.remove(listener);
+
+      if (listeners.size() == 0)
+        listeners = null;
+    }
+  }
+
+  /**
+   * Gets the TabbedPanelProperties
+   *
+   * @return the TabbedPanelProperties for this tabbed panel
+   */
+  public TabbedPanelProperties getProperties() {
+    return properties;
+  }
+
+  /**
+   * Checks if this tabbed panel has a content area
+   *
+   * @return true if content area exist, otherwise false
+   * @since ITP 1.3.0
+   */
+  public boolean hasContentArea() {
+    return contentPanel != null;
+  }
+
+  DraggableComponentBox getDraggableComponentBox() {
+    return draggableComponentBox;
+  }
+
+  private void initialize(JComponent contentPanel) {
+    setLayout(new BorderLayout());
+
+    shadowRepaintChecker = new ComponentPaintChecker(this);
+
+    setOpaque(false);
+
+    draggableComponentBox.setOuterParentArea(tabAreaContainer);
+    tabAreaContainer.add(tabAreaComponentsPanel);
+    tabAreaContainer.add(draggableComponentBox);
+
+    this.contentPanel = contentPanel;
+    draggableComponentBox.addListener(draggableComponentBoxListener);
+
+    if (contentPanel != null) {
+      componentsPanel.add(contentPanel, BorderLayout.CENTER);
+    }
+
+    add(componentsPanel, BorderLayout.CENTER);
+
+    updateAllDefaultValues();
+
+    PropertyMapWeakListenerManager.addWeakTreeListener(properties.getMap(), propertyChangedListener);
+    //updateProperties(null);
+  }
+
+  /*private void updateProperties(Map changes) {
+    //componentsPanel.remove(draggableComponentBox);
+    tabAreaOrientation = properties.getTabAreaOrientation();
+    updateTabArea();
+    updateAllTabsProperties();
+
+    componentsPanel.add(tabAreaContainer, ComponentUtil.getBorderLayoutOrientation(tabAreaOrientation));
+
+    // Shadow
+    shadowSize = properties.getShadowSize();
+    componentsPanel.setBorder(
+        contentPanel != null && properties.getShadowEnabled() ? new EmptyBorder(0, 0, shadowSize, shadowSize) : null);
+
+    checkOnlyOneTab(true);
+
+    updateScrollButtons();
+    updateTabDropDownList();
+
+    //repaint();
+    //revalidate();
+  }*/
+
+  private void updateTabDropDownList() {
+    TabDropDownListVisiblePolicy newListVisiblePolicy = properties.getTabDropDownListVisiblePolicy();
+    TabLayoutPolicy newListTabLayoutPolicy = properties.getTabLayoutPolicy();
+
+    if (newListVisiblePolicy != listVisiblePolicy || newListTabLayoutPolicy != listTabLayoutPolicy) {
+      if (dropDownList != null) {
+        tabAreaComponentsPanel.remove(dropDownList);
+        dropDownList.dispose();
+        dropDownList = null;
+      }
+
+      if (newListVisiblePolicy == TabDropDownListVisiblePolicy.MORE_THAN_ONE_TAB ||
+          (newListVisiblePolicy == TabDropDownListVisiblePolicy.TABS_NOT_VISIBLE &&
+           newListTabLayoutPolicy == TabLayoutPolicy.SCROLLING)) {
+        dropDownList = new TabDropDownList(this, properties.getButtonProperties().getTabDropDownListButtonProperties()
+                                                 .applyTo(properties.getButtonProperties()
+                                                          .getTabDropDownListButtonProperties()
+                                                          .getFactory()
+                                                          .createButton(this)));
+        tabAreaComponentsPanel.add(dropDownList, scrollButtonBox == null ? 0 : 1);
+
+        if (newListVisiblePolicy == TabDropDownListVisiblePolicy.TABS_NOT_VISIBLE)
+          dropDownList.setVisible(false);
+      }
+    }
+
+    listVisiblePolicy = newListVisiblePolicy;
+    listTabLayoutPolicy = newListTabLayoutPolicy;
+
+    if (dropDownList != null && !draggableComponentBox.isScrollEnabled() &&
+        listVisiblePolicy == TabDropDownListVisiblePolicy.TABS_NOT_VISIBLE)
+      dropDownList.setVisible(false);
+
+    tabAreaComponentsPanel.revalidate();
+  }
+
+  private void updateAllTabsProperties() {
+    Component[] components = draggableComponentBox.getBoxComponents();
+    for (int i = 0; i < components.length; i++)
+      updateTabProperties((Tab) components[i]);
+  }
+
+  private void updateTabProperties(Tab tab) {
+    tab.getDraggableComponent().setAbortDragKeyCode(properties.getAbortDragKey());
+    tab.getDraggableComponent().setReorderEnabled(properties.getTabReorderEnabled());
+    tab.getDraggableComponent().setSelectOnMousePress(properties.getTabSelectTrigger() == TabSelectTrigger.MOUSE_PRESS);
+  }
+
+  private void updateTabAreaComponentsPanel(Direction direction, int alignmentX, int alignmentY) {
+    ((DirectionLayout) tabAreaComponentsPanel.getLayout()).setDirection(direction);
+  }
+
+  private void updateShapedPanelProperties(ShapedPanel panel,
+                                           ShapedPanelProperties shapedPanelProperties) {
+
+    InternalPropertiesUtil.applyTo(shapedPanelProperties, panel, properties.getTabAreaOrientation().getNextCW());
+  }
+
+  private void setTabAreaLayoutConstraints(JComponent c,
+                                           int gridx,
+                                           int gridy,
+                                           int fill,
+                                           double weightx,
+                                           double weighty,
+                                           int anchor) {
+    constraints.gridx = gridx;
+    constraints.gridy = gridy;
+    constraints.fill = fill;
+    constraints.weightx = weightx;
+    constraints.weighty = weighty;
+    constraints.anchor = anchor;
+
+    tabAreaLayoutManager.setConstraints(c, constraints);
+  }
+
+  private void doInsertTab(Tab tab, Point p, int index) {
+    if (tab != null && !draggableComponentBox.containsDraggableComponent(tab.getDraggableComponent())) {
+      tab.setTabbedPanel(this);
+      if (p != null)
+        draggableComponentBox.insertDraggableComponent(tab.getDraggableComponent(),
+                                                       SwingUtilities.convertPoint(this, p, draggableComponentBox));
+      else
+        draggableComponentBox.insertDraggableComponent(tab.getDraggableComponent(), index);
+      updateTabProperties(tab);
+      checkIfOnlyOneTab(true);
+    }
+  }
+
+  private Tab findTab(DraggableComponent draggableComponent) {
+    return draggableComponent == null ? null : (Tab) draggableComponent.getComponent();
+  }
+
+  private void checkIfOnlyOneTab(boolean inc) {
+    if (getTabCount() == 1) {
+      draggableComponentBox.setScrollEnabled(false);
+      updateScrollButtons();
+    }
+    else if (inc && getTabCount() == 2) {
+      draggableComponentBox.setScrollEnabled(properties.getTabLayoutPolicy() == TabLayoutPolicy.SCROLLING);
+      updateScrollButtons();
+      updateTabDropDownList();
+    }
+  }
+
+  private void setTabAreaComponentsButtonsVisible() {
+    if (scrollButtonBox != null) {
+      boolean visible = false;
+      if (!tabAreaOrientation.isHorizontal())
+        visible = draggableComponentBox.getInnerSize().getWidth() > calcScrollWidth();
+      else
+        visible = draggableComponentBox.getInnerSize().getHeight() > calcScrollHeight();
+      scrollButtonBox.setVisible(visible);
+
+      if (dropDownList != null && listVisiblePolicy == TabDropDownListVisiblePolicy.TABS_NOT_VISIBLE)
+        dropDownList.setVisible(visible);
+
+      if (!visible) {
+        scrollButtonBox.setButton1Enabled(false);
+        scrollButtonBox.setButton2Enabled(true);
+      }
+    }
+    else {
+      if (dropDownList != null && listVisiblePolicy == TabDropDownListVisiblePolicy.TABS_NOT_VISIBLE)
+        dropDownList.setVisible(false);
+    }
+
+    tabAreaComponentsPanel.setVisible(ComponentUtil.hasVisibleChildren(tabAreaComponentsPanel));
+  }
+
+  private int calcScrollWidth() {
+    Insets componentsPanelInsets = tabAreaComponentsPanel.getInsets();
+    boolean includeDropDownWidth = dropDownList != null && listVisiblePolicy == TabDropDownListVisiblePolicy.TABS_NOT_VISIBLE;
+    boolean componentsVisible = includeDropDownWidth
+                                ? ComponentUtil.isOnlyVisibleComponents(new Component[]{scrollButtonBox, dropDownList})
+                                : ComponentUtil.isOnlyVisibleComponent(scrollButtonBox);
+    int insetsWidth = tabAreaComponentsPanel.isVisible() && componentsVisible ?
+                      componentsPanelInsets.left + componentsPanelInsets.right : 0;
+    int componentsPanelWidth = tabAreaComponentsPanel.isVisible() ?
+                               ((int) tabAreaComponentsPanel.getPreferredSize().getWidth() - insetsWidth - (scrollButtonBox.isVisible()
+                                                                                                            ?
+                                                                                                            scrollButtonBox.getWidth() +
+                                                                                                            (includeDropDownWidth ?
+                                                                                                             dropDownList.getWidth() :
+                                                                                                             0)
+                                                                                                            :
+                                                                                                            0)) :
+                               0;
+    Insets areaInsets = tabAreaContainer.getInsets();
+    return tabAreaContainer.getWidth() - componentsPanelWidth - areaInsets.left - areaInsets.right;
+  }
+
+  private int calcScrollHeight() {
+    Insets componentsPanelInsets = tabAreaComponentsPanel.getInsets();
+    boolean includeDropDownHeight = listVisiblePolicy == TabDropDownListVisiblePolicy.TABS_NOT_VISIBLE;
+    boolean componentsVisible = includeDropDownHeight
+                                ? ComponentUtil.isOnlyVisibleComponents(new Component[]{scrollButtonBox, dropDownList})
+                                : ComponentUtil.isOnlyVisibleComponent(scrollButtonBox);
+    int insetsHeight = tabAreaComponentsPanel.isVisible() && componentsVisible ?
+                       componentsPanelInsets.top + componentsPanelInsets.bottom : 0;
+    int componentsPanelHeight = tabAreaComponentsPanel.isVisible() ?
+                                ((int) tabAreaComponentsPanel.getPreferredSize().getHeight() - insetsHeight - (scrollButtonBox.isVisible()
+                                                                                                               ?
+                                                                                                               scrollButtonBox.getHeight() +
+                                                                                                               (includeDropDownHeight ?
+                                                                                                                dropDownList.getHeight() :
+                                                                                                                0)
+                                                                                                               :
+                                                                                                               0)) :
+                                0;
+    Insets areaInsets = tabAreaContainer.getInsets();
+    return tabAreaContainer.getHeight() - componentsPanelHeight - areaInsets.top - areaInsets.bottom;
+  }
+
+  private void updateScrollButtons() {
+    ScrollButtonBox oldScrollButtonBox = scrollButtonBox;
+    scrollButtonBox = draggableComponentBox.getScrollButtonBox();
+    if (oldScrollButtonBox != scrollButtonBox) {
+      if (oldScrollButtonBox != null) {
+        tabAreaComponentsPanel.remove(oldScrollButtonBox);
+      }
+
+      if (scrollButtonBox != null) {
+        scrollButtonBox.setButtons(properties.getButtonProperties().getScrollUpButtonProperties().applyTo(
+            properties.getButtonProperties().getScrollUpButtonProperties().getFactory().createButton(this)),
+                                   properties.getButtonProperties().getScrollDownButtonProperties().applyTo(
+                                       properties.getButtonProperties().getScrollDownButtonProperties().getFactory()
+                                       .createButton(this)),
+                                   properties.getButtonProperties().getScrollLeftButtonProperties().applyTo(
+                                       properties.getButtonProperties().getScrollLeftButtonProperties().getFactory()
+                                       .createButton(this)),
+                                   properties.getButtonProperties().getScrollRightButtonProperties().applyTo(
+                                       properties.getButtonProperties().getScrollRightButtonProperties().getFactory()
+                                       .createButton(this)));
+        scrollButtonBox.setVisible(false);
+        tabAreaComponentsPanel.add(scrollButtonBox, 0);
+      }
+
+      tabAreaComponentsPanel.revalidate();
+    }
+  }
+
+  private void fireTabMoved(Tab tab) {
+    if (listeners != null) {
+      TabEvent event = new TabEvent(this, tab);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabMoved(event);
+    }
+  }
+
+  private void fireDraggedEvent(Tab tab, MouseEvent mouseEvent) {
+    if (listeners != null) {
+      TabDragEvent event = new TabDragEvent(this, EventUtil.convert(mouseEvent, tab));
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabDragged(event);
+    }
+  }
+
+  private void fireDroppedEvent(Tab tab, MouseEvent mouseEvent) {
+    if (listeners != null) {
+      TabDragEvent event = new TabDragEvent(this, EventUtil.convert(mouseEvent, tab));
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabDropped(event);
+    }
+  }
+
+  private void fireNotDroppedEvent(Tab tab) {
+    if (listeners != null) {
+      TabEvent event = new TabEvent(this, tab);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabDragAborted(event);
+    }
+  }
+
+  private void fireSelectedEvent(Tab tab, Tab oldTab) {
+    if (listeners != null) {
+      {
+        TabStateChangedEvent event = new TabStateChangedEvent(this, this, oldTab, oldTab, tab);
+        Object[] l = listeners.toArray();
+        for (int i = 0; i < l.length; i++)
+          ((TabListener) l[i]).tabDeselected(event);
+      }
+      {
+        TabStateChangedEvent event = new TabStateChangedEvent(this, this, tab, oldTab, tab);
+        Object[] l = listeners.toArray();
+        for (int i = 0; i < l.length; i++)
+          ((TabListener) l[i]).tabSelected(event);
+      }
+    }
+  }
+
+  private void fireHighlightedEvent(Tab tab, Tab oldTab) {
+    if (listeners != null) {
+      {
+        TabStateChangedEvent event = new TabStateChangedEvent(this, this, oldTab, oldTab, tab);
+        Object[] l = listeners.toArray();
+        for (int i = 0; i < l.length; i++)
+          ((TabListener) l[i]).tabDehighlighted(event);
+      }
+      {
+        TabStateChangedEvent event = new TabStateChangedEvent(this, this, tab, oldTab, tab);
+        Object[] l = listeners.toArray();
+        for (int i = 0; i < l.length; i++)
+          ((TabListener) l[i]).tabHighlighted(event);
+      }
+    }
+  }
+
+  private void fireAddedEvent(Tab tab) {
+    if (listeners != null) {
+      TabEvent event = new TabEvent(this, tab);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabAdded(event);
+    }
+  }
+
+  private void fireRemovedEvent(Tab tab) {
+    if (listeners != null) {
+      TabRemovedEvent event = new TabRemovedEvent(this, tab, this);
+      Object[] l = listeners.toArray();
+      for (int i = 0; i < l.length; i++)
+        ((TabListener) l[i]).tabRemoved(event);
+    }
+  }
+
+  protected void processMouseEvent(MouseEvent event) {
+    if (event.getID() == MouseEvent.MOUSE_ENTERED) {
+      if (!mouseEntered) {
+        mouseEntered = true;
+        super.processMouseEvent(event);
+      }
+    }
+    else if (event.getID() == MouseEvent.MOUSE_EXITED) {
+      if (!contains(event.getPoint())) {
+        mouseEntered = false;
+        super.processMouseEvent(event);
+      }
+    }
+    else
+      super.processMouseEvent(event);
+  }
+
+  void doProcessMouseEvent(MouseEvent event) {
+    processMouseEvent(SwingUtilities.convertMouseEvent((Component) event.getSource(), event, this));
+  }
+
+  void doProcessMouseMotionEvent(MouseEvent event) {
+    processMouseMotionEvent(SwingUtilities.convertMouseEvent((Component) event.getSource(), event, this));
+  }
+
+  private void updateShadow() {
+    if (shadowRepaintChecker.isPaintingOk() && contentPanel != null && properties.getShadowEnabled()) {
+      Point p = SwingUtilities.convertPoint(tabAreaContainer, new Point(0, 0), this);
+      repaint(p.x, p.y, tabAreaContainer.getWidth() + shadowSize, tabAreaContainer.getHeight() + shadowSize);
+    }
+  }
+
+  private void updatePanelOpaque() {
+    if (!properties.getShadowEnabled()
+        && properties.getTabAreaProperties().getShapedPanelProperties()
+        .getOpaque()
+        && (contentPanel == null ? true : properties
+        .getContentPanelProperties().getShapedPanelProperties().getOpaque())) {
+      BaseContainerUtil.setForcedOpaque(componentsPanel, true);
+      setOpaque(true);
+    }
+    else {
+      BaseContainerUtil.setForcedOpaque(componentsPanel, false);
+      setOpaque(false);
+    }
+  }
+
+  private class ShadowPanel extends HoverablePanel {
+    ShadowPanel() {
+      super(new BorderLayout(), properties.getHoverListener());
+      setCursor(null);
+    }
+
+    public boolean contains(int x, int y) {
+      return properties.getShadowEnabled() ? doContains(x, y) : super.contains(x, y);
+    }
+
+    public boolean inside(int x, int y) {
+      return properties.getShadowEnabled() ? doContains(x, y) : super.inside(x, y);
+    }
+
+    private boolean doContains(int x, int y) {
+      Dimension d = DimensionUtil.getInnerDimension(getSize(), getInsets());
+      return x >= 0 && y >= 0 && x < d.getWidth() && y < d.getHeight();
+    }
+
+    public void paint(Graphics g) {
+      super.paint(g);
+
+      if (contentPanel == null || !properties.getShadowEnabled())
+        return;
+
+      new ShadowPainter(this,
+                        componentsPanel,
+                        highlightedTab,
+                        contentPanel,
+                        tabAreaComponentsPanel,
+                        tabAreaContainer,
+                        draggableComponentBox,
+                        properties.getTabAreaOrientation(),
+                        properties.getPaintTabAreaShadow(),
+                        shadowSize,
+                        properties.getShadowBlendAreaSize(),
+                        properties.getShadowColor(),
+                        properties.getShadowStrength(),
+                        getTabIndex(getHighlightedTab()) == getTabCount() - 1).paint(g);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/TabbedPanelButtonProperties.java b/src/net/infonode/tabbedpanel/TabbedPanelButtonProperties.java
new file mode 100644
index 0000000..af4b140
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedPanelButtonProperties.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelButtonProperties.java,v 1.3 2005/02/16 11:28:15 jesper Exp $
+
+package net.infonode.tabbedpanel;
+
+import net.infonode.properties.gui.util.ButtonProperties;
+import net.infonode.properties.propertymap.*;
+
+/**
+ * Tabbed panel button properties contains properties objects for all buttons in a tabbed panel.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ * @since ITP 1.3.0
+ */
+public class TabbedPanelButtonProperties extends PropertyMapContainer {
+
+  /**
+   * A property group for all button properties in a tabbed panel
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Tabbed Panel Button Properties",
+                                                                         "Properties for a Tabbed Panel's buttons.");
+
+  /**
+   * Properties for scroll up button
+   */
+  public static final PropertyMapProperty SCROLL_UP_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                                "Scroll Up Button Properties",
+                                                                                                "Properties for scroll up button.",
+                                                                                                ButtonProperties.PROPERTIES);
+
+  /**
+   * Properties for scroll left button
+   */
+  public static final PropertyMapProperty SCROLL_LEFT_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                                  "Scroll Left Button Properties",
+                                                                                                  "Properties for scroll left button.",
+                                                                                                  ButtonProperties.PROPERTIES);
+
+  /**
+   * Properties for scroll down button
+   */
+  public static final PropertyMapProperty SCROLL_DOWN_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                                  "Scroll Down Button Properties",
+                                                                                                  "Properties for scroll down button.",
+                                                                                                  ButtonProperties.PROPERTIES);
+
+  /**
+   * Properties for scroll right button
+   */
+  public static final PropertyMapProperty SCROLL_RIGHT_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                                   "Scroll Right Button Properties",
+                                                                                                   "Properties for scroll right button.",
+                                                                                                   ButtonProperties.PROPERTIES);
+
+  /**
+   * Properties for tab drop down list button
+   */
+  public static final PropertyMapProperty TAB_DROP_DOWN_LIST_BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                                         "Tab Drop Down List Button Properties",
+                                                                                                         "Properties for the tab drop down list button.",
+                                                                                                         ButtonProperties.PROPERTIES);
+
+  /**
+   * Constructs an empty TabbedPanelButtonProperties object
+   */
+  public TabbedPanelButtonProperties() {
+    super(PROPERTIES);
+  }
+
+  /**
+   * Constructs a TabbedPanelButtonProperties object with the given object
+   * as property storage
+   *
+   * @param object object to store properties in
+   */
+  public TabbedPanelButtonProperties(PropertyMap object) {
+    super(object);
+  }
+
+  /**
+   * Constructs a TabbedPanelButtonProperties object that inherits its properties
+   * from the given TabbedPanelButtonProperties object
+   *
+   * @param inheritFrom TabbedPanelButtonProperties object to inherit properties from
+   */
+  public TabbedPanelButtonProperties(TabbedPanelButtonProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param superObject the object from which to inherit property values
+   * @return this
+   */
+  public TabbedPanelButtonProperties addSuperObject(TabbedPanelButtonProperties superObject) {
+    getMap().addSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   */
+  public TabbedPanelButtonProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes the given super object.
+   *
+   * @param superObject super object to remove
+   * @return this
+   * @since ITP 1.3.0
+   */
+  public TabbedPanelButtonProperties removeSuperObject(TabbedPanelButtonProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Gets the scroll up button properties
+   *
+   * @return the scroll up button properties
+   */
+  public ButtonProperties getScrollUpButtonProperties() {
+    return new ButtonProperties(SCROLL_UP_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the scroll down button properties
+   *
+   * @return the scroll down button properties
+   */
+  public ButtonProperties getScrollDownButtonProperties() {
+    return new ButtonProperties(SCROLL_DOWN_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the scroll left button properties
+   *
+   * @return the scroll up button properties
+   */
+  public ButtonProperties getScrollLeftButtonProperties() {
+    return new ButtonProperties(SCROLL_LEFT_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the scroll right button properties
+   *
+   * @return the scroll right button properties
+   */
+  public ButtonProperties getScrollRightButtonProperties() {
+    return new ButtonProperties(SCROLL_RIGHT_BUTTON_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the tab drop down list button properties
+   *
+   * @return the tab drop down list button properties
+   */
+  public ButtonProperties getTabDropDownListButtonProperties() {
+    return new ButtonProperties(TAB_DROP_DOWN_LIST_BUTTON_PROPERTIES.get(getMap()));
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/TabbedPanelContentPanel.java b/src/net/infonode/tabbedpanel/TabbedPanelContentPanel.java
new file mode 100644
index 0000000..aec7364
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedPanelContentPanel.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelContentPanel.java,v 1.60 2009/02/05 15:57:55 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import java.awt.BorderLayout;
+import java.awt.Rectangle;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.Map;
+
+import javax.swing.JComponent;
+import javax.swing.SwingUtilities;
+
+import net.infonode.gui.ComponentPaintChecker;
+import net.infonode.gui.draggable.DraggableComponentBoxAdapter;
+import net.infonode.gui.draggable.DraggableComponentBoxEvent;
+import net.infonode.gui.draggable.DraggableComponentEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.gui.hover.panel.HoverableShapedPanel;
+import net.infonode.gui.panel.BaseContainer;
+import net.infonode.gui.panel.BaseContainerUtil;
+import net.infonode.properties.gui.InternalPropertiesUtil;
+import net.infonode.properties.propertymap.PropertyMapTreeListener;
+import net.infonode.properties.propertymap.PropertyMapWeakListenerManager;
+import net.infonode.tabbedpanel.internal.TabbedHoverUtil;
+import net.infonode.util.Direction;
+import net.infonode.util.ValueChange;
+
+/**
+ * A TabbedPanelContentPanel is a component that holds a container for tab content
+ * components. It can be configured using properties that specifies the look for
+ * the content panel.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.60 $
+ * @see TabbedPanel
+ * @see Tab
+ */
+public class TabbedPanelContentPanel extends BaseContainer {
+  private final TabbedPanel tabbedPanel;
+  private final HoverableShapedPanel shapedPanel;
+  private final ComponentPaintChecker repaintChecker;
+
+  private final PropertyMapTreeListener propertiesListener = new PropertyMapTreeListener() {
+    public void propertyValuesChanged(Map changes) {
+      Map m = (Map) changes.get(tabbedPanel.getProperties().getContentPanelProperties().getMap());
+      if (m != null) {
+        if (m.keySet().contains(TabbedPanelContentPanelProperties.HOVER_LISTENER)) {
+          shapedPanel.setHoverListener(
+              (HoverListener) ((ValueChange) m.get(TabbedPanelContentPanelProperties.HOVER_LISTENER)).getNewValue());
+        }
+      }
+
+      m = (Map) changes.get(tabbedPanel.getProperties().getContentPanelProperties().getComponentProperties().getMap());
+      if (m != null)
+        update();
+
+      m = (Map) changes.get(
+          tabbedPanel.getProperties().getContentPanelProperties().getShapedPanelProperties().getMap());
+      if (m != null)
+        update();
+
+      m = (Map) changes.get(tabbedPanel.getProperties().getMap());
+      if (m != null && m.keySet().contains(TabbedPanelProperties.TAB_AREA_ORIENTATION)) {
+        shapedPanel.setDirection(
+            ((Direction) ((ValueChange) m.get(TabbedPanelProperties.TAB_AREA_ORIENTATION)).getNewValue()).getNextCW());
+      }
+    }
+  };
+
+  /**
+   * Constructs a TabbedPanelContentPanel
+   *
+   * @param tabbedPanel the TabbedPanel that this content panel should be the content
+   *                    area component for
+   * @param component   a component used as container for the tabs' content components
+   */
+  public TabbedPanelContentPanel(final TabbedPanel tabbedPanel, JComponent component) {
+    super(new BorderLayout());
+    this.tabbedPanel = tabbedPanel;
+
+    shapedPanel = new HoverableShapedPanel(new BorderLayout(),
+        tabbedPanel.getProperties().getContentPanelProperties().getHoverListener(),
+        tabbedPanel) {
+      public boolean acceptHover(ArrayList enterableHoverables) {
+        return TabbedHoverUtil.acceptTabbedPanelHover(getTabbedPanel().getProperties().getHoverPolicy(),
+            enterableHoverables,
+            getTabbedPanel(),
+            this);
+      }
+
+      protected void processMouseEvent(MouseEvent event) {
+        super.processMouseEvent(event);
+        if (getTabbedPanel().hasContentArea())
+          getTabbedPanel().doProcessMouseEvent(event);
+        else
+          doProcessMouseEvent(SwingUtilities.convertMouseEvent(this, event, TabbedPanelContentPanel.this));
+      }
+
+      protected void processMouseMotionEvent(MouseEvent event) {
+        super.processMouseMotionEvent(event);
+
+        if (getTabbedPanel().hasContentArea())
+          getTabbedPanel().doProcessMouseMotionEvent(event);
+        else
+          doProcessMouseMotionEvent(SwingUtilities.convertMouseEvent(this, event, TabbedPanelContentPanel.this));
+      }
+    };
+
+    repaintChecker = new ComponentPaintChecker(shapedPanel);
+
+    shapedPanel.add(component, BorderLayout.CENTER);
+    add(shapedPanel, BorderLayout.CENTER);
+    update();
+
+    PropertyMapWeakListenerManager.addWeakTreeListener(tabbedPanel.getProperties().getMap(), propertiesListener);
+
+    tabbedPanel.getDraggableComponentBox().addListener(new DraggableComponentBoxAdapter() {
+      public void changed(DraggableComponentBoxEvent event) {
+        if (event.getDraggableComponent() == null || event.getDraggableComponentEvent().getType() == DraggableComponentEvent.TYPE_UNDEFINED) {
+          repaintBorder();
+        }
+      }
+    });
+
+    tabbedPanel.addTabListener(new TabAdapter() {
+      public void tabAdded(TabEvent event) {
+        repaintBorder();
+      }
+
+      public void tabRemoved(TabRemovedEvent event) {
+        repaintBorder();
+      }
+
+      public void tabSelected(TabStateChangedEvent event) {
+        repaintBorder();
+      }
+
+      public void tabDeselected(TabStateChangedEvent event) {
+        repaintBorder();
+      }
+
+      public void tabDehighlighted(TabStateChangedEvent event) {
+        repaintBorder();
+      }
+
+      public void tabHighlighted(TabStateChangedEvent event) {
+        repaintBorder();
+      }
+
+      public void tabMoved(TabEvent event) {
+        repaintBorder();
+      }
+    });
+  }
+
+  /**
+   * Gets the tabbed panel that this component is the content area component for
+   *
+   * @return the tabbed panel
+   */
+  public TabbedPanel getTabbedPanel() {
+    return tabbedPanel;
+  }
+
+  /**
+   * Gets the properties for this component
+   *
+   * @return the properties for this TabbedPanelContentPanel
+   */
+  public TabbedPanelContentPanelProperties getProperties() {
+    return tabbedPanel.getProperties().getContentPanelProperties();
+  }
+
+  private void update() {
+    getProperties().getComponentProperties().applyTo(shapedPanel);
+    InternalPropertiesUtil.applyTo(getProperties().getShapedPanelProperties(),
+        shapedPanel,
+        tabbedPanel.getProperties().getTabAreaOrientation().getNextCW());
+    BaseContainerUtil.setForcedOpaque(this, getProperties().getShapedPanelProperties().getOpaque());
+    //setForcedOpaque(getProperties().getShapedPanelProperties().getOpaque());
+  }
+
+  private void repaintBorder() {
+    if (repaintChecker.isPaintingOk()) {
+      final Rectangle r;
+
+      Direction d = tabbedPanel.getProperties().getTabAreaOrientation();
+
+      if (d == Direction.UP)
+        r = new Rectangle(0, 0, shapedPanel.getWidth(), shapedPanel.getInsets().top);
+      else if (d == Direction.LEFT)
+        r = new Rectangle(0, 0, shapedPanel.getInsets().left, shapedPanel.getHeight());
+      else if (d == Direction.DOWN)
+        r = new Rectangle(0,
+            shapedPanel.getHeight() - shapedPanel.getInsets().bottom - 1,
+            shapedPanel.getWidth(),
+            shapedPanel.getHeight());
+      else
+        r = new Rectangle(shapedPanel.getWidth() - shapedPanel.getInsets().right - 1,
+            0,
+            shapedPanel.getWidth(),
+            shapedPanel.getHeight());
+
+      shapedPanel.repaint(r);
+    }
+  }
+
+  private void doProcessMouseEvent(MouseEvent event) {
+    processMouseEvent(event);
+  }
+
+  private void doProcessMouseMotionEvent(MouseEvent event) {
+    processMouseMotionEvent(event);
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabbedPanelContentPanelProperties.java b/src/net/infonode/tabbedpanel/TabbedPanelContentPanelProperties.java
new file mode 100644
index 0000000..db31ae8
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedPanelContentPanelProperties.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelContentPanelProperties.java,v 1.27 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.HoverListenerProperty;
+
+/**
+ * TabbedPanelContentPanelProperties holds all properties for a
+ * {@link TabbedPanelContentPanel}. These properties affects the
+ * content area of a TabbedPanel. TabbedPanelProperties contains
+ * TabbedPanelContentPanelProperties.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.27 $
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ */
+public class TabbedPanelContentPanelProperties extends PropertyMapContainer {
+  /**
+   * A property group for all properties in TabbedPanelContentPanelProperties
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Tab Content Panel Properties",
+                                                                         "Properties for the TabContentPanel class.");
+
+  /**
+   * Properties for the component
+   *
+   * @see #getComponentProperties
+   */
+  public static final PropertyMapProperty COMPONENT_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                         "Component Properties",
+                                                                                         "Properties for the content area component.",
+                                                                                         ComponentProperties.PROPERTIES);
+
+  /**
+   * Properties for the shaped panel
+   *
+   * @see #getShapedPanelProperties
+   * @since ITP 1.2.0
+   */
+  public static final PropertyMapProperty SHAPED_PANEL_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                            "Shaped Panel Properties",
+                                                                                            "Properties for shaped tab area components area.",
+                                                                                            ShapedPanelProperties.PROPERTIES);
+
+  /**
+   * Hover listener property
+   *
+   * @see #setHoverListener
+   * @see #getHoverListener
+   * @since ITP 1.3.0
+   */
+  public static final HoverListenerProperty HOVER_LISTENER = new HoverListenerProperty(PROPERTIES,
+                                                                                       "Hover Listener",
+                                                                                       "Hover Listener to be used for tracking mouse hovering over the content area.",
+                                                                                       PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Constructs an empty TabbedPanelContentPanelProperties object
+   */
+  public TabbedPanelContentPanelProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Constructs a TabbedPanelContentPanelProperties map with the given map
+   * as property storage
+   *
+   * @param map map to store properties in
+   */
+  public TabbedPanelContentPanelProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Constructs a TabbedPanelContentPanelProperties object that inherits its properties
+   * from the given TabbedPanelContentPanelProperties object
+   *
+   * @param inheritFrom TabbedPanelContentPanelProperties object to inherit properties
+   *                    from
+   */
+  public TabbedPanelContentPanelProperties(TabbedPanelContentPanelProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param superObject the object from which to inherit property values
+   * @return this
+   */
+  public TabbedPanelContentPanelProperties addSuperObject(TabbedPanelContentPanelProperties superObject) {
+    getMap().addSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   */
+  public TabbedPanelContentPanelProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes the given super object.
+   *
+   * @param superObject super object to remove
+   * @return this
+   * @since ITP 1.3.0
+   */
+  public TabbedPanelContentPanelProperties removeSuperObject(TabbedPanelContentPanelProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Gets the component properties
+   *
+   * @return component properties
+   */
+  public ComponentProperties getComponentProperties() {
+    return new ComponentProperties(COMPONENT_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the shaped panel properties
+   *
+   * @return shaped panel properties
+   * @since ITP 1.2.0
+   */
+  public ShapedPanelProperties getShapedPanelProperties() {
+    return new ShapedPanelProperties(SHAPED_PANEL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * <p>Sets the hover listener that will be triggered when the content area is hoverd by the mouse.</p>
+   *
+   * <p>The tabbed panel that the hovered content area is part of will be the source of the hover event sent to the
+   * hover listener.</p>
+   *
+   * @param listener the hover listener
+   * @return this TabbedPanelContentPanelProperties
+   * @since ITP 1.3.0
+   */
+  public TabbedPanelContentPanelProperties setHoverListener(HoverListener listener) {
+    HOVER_LISTENER.set(getMap(), listener);
+    return this;
+  }
+
+  /**
+   * <p>Gets the hover listener that will be triggered when the content area is hovered by the mouse.</p>
+   *
+   * <p>The tabbed panel that the hovered content area is part of will be the source of the hover event sent to the
+   * hover listener.</p>
+   *
+   * @return the hover listener
+   * @since ITP 1.3.0
+   */
+  public HoverListener getHoverListener() {
+    return HOVER_LISTENER.get(getMap());
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabbedPanelDefaultButtonFactories.java b/src/net/infonode/tabbedpanel/TabbedPanelDefaultButtonFactories.java
new file mode 100644
index 0000000..2e24d23
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedPanelDefaultButtonFactories.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelDefaultButtonFactories.java,v 1.2 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.gui.button.ButtonFactory;
+import net.infonode.gui.button.FlatButtonFactory;
+
+/**
+ * Contains the default tabbed panel button factories.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.2 $
+ * @since ITP 1.3.0
+ */
+public class TabbedPanelDefaultButtonFactories {
+  private static final FlatButtonFactory BUTTON_FACTORY = new FlatButtonFactory();
+
+  /**
+   * Returns the default scroll up button factory.
+   *
+   * @return the default scroll up button factory
+   */
+  public static ButtonFactory getScrollUpButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+
+  /**
+   * Returns the default scroll down button factory.
+   *
+   * @return the default scroll down button factory
+   */
+  public static ButtonFactory getScrollDownButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+
+
+  /**
+   * Returns the default scroll left button factory.
+   *
+   * @return the default scroll left button factory
+   */
+  public static ButtonFactory getScrollLeftButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+
+  /**
+   * Returns the default scroll right button factory.
+   *
+   * @return the default scroll right button factory
+   */
+  public static ButtonFactory getScrollRightButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+
+  /**
+   * Returns the default tab drop down list button factory.
+   *
+   * @return the default tab drop down list button factory
+   */
+  public static ButtonFactory getTabDropDownListButtonFactory() {
+    return BUTTON_FACTORY;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabbedPanelHoverPolicy.java b/src/net/infonode/tabbedpanel/TabbedPanelHoverPolicy.java
new file mode 100644
index 0000000..e720161
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedPanelHoverPolicy.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelHoverPolicy.java,v 1.6 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.util.Enum;
+
+/**
+ * TabbedPanelHoverPolicy defines the hover policy, i.e. when a tabbed panel should consider itself
+ * hovered by the mouse and the HoverListener should be called. This policy affects the tabbed panel,
+ * the tab area, the tab area components area and the content area (if the tabbed panel has a content
+ * area).
+ *
+ * @author johan
+ * @version $Revision: 1.6 $
+ * @see net.infonode.gui.hover.HoverListener
+ * @since ITP 1.3.0
+ */
+public class TabbedPanelHoverPolicy extends Enum {
+
+  private static final long serialVersionUID = 1;
+
+  /**
+   * Never hover policy. This means that the tabbed panel will nerver be considered hovered
+   */
+  public static final TabbedPanelHoverPolicy NEVER = new TabbedPanelHoverPolicy(0, "Never Hover");
+
+  /**
+   * Always hover policy. This means that the tabbed panel will always consider itself hovered
+   * when the mouse is over the tabbed panel.
+   */
+  public static final TabbedPanelHoverPolicy ALWAYS = new TabbedPanelHoverPolicy(1, "Always Hover");
+
+  /**
+   * No hovered child hover policy. This means that the tabbed panel will consider itself hovered when
+   * the mouse is over the tabbed panel and the content area doesn't contain any hovered tabbed panel.
+   */
+  public static final TabbedPanelHoverPolicy NO_HOVERED_CHILD = new TabbedPanelHoverPolicy(2,
+                                                                                           "No Hovered Child Tabbed Panel");
+
+  /**
+   * Only when deepest hover policy. This means that the tabbed panel will consider itself hovered when
+   * the mouse is over the tabbed panel and there is no other tabbed panel in the tabbed panel's content area.
+   */
+  public static final TabbedPanelHoverPolicy ONLY_WHEN_DEEPEST = new TabbedPanelHoverPolicy(3,
+                                                                                            "Only when Deepest Tabbed Panel");
+
+  /**
+   * Always and exclude hover policy. This means that the tabbed panel will always consider itself hovered
+   * when the mouse is over the tabbed panel but it will be excluded by other tabbed panels when their hover policies
+   * are evaluated.
+   *
+   * @since ITP 1.4.0
+   */
+  public static final TabbedPanelHoverPolicy ALWAYS_AND_EXCLUDE = new TabbedPanelHoverPolicy(4,
+                                                                                             "Always Hover and be Excluded by Others");
+
+  private TabbedPanelHoverPolicy(int value, String name) {
+    super(value, name);
+  }
+
+  /**
+   * Gets the hover policies.
+   *
+   * @return the hover policies
+   */
+  public static TabbedPanelHoverPolicy[] getHoverPolicies() {
+    return new TabbedPanelHoverPolicy[]{NEVER, ALWAYS, NO_HOVERED_CHILD, ONLY_WHEN_DEEPEST, ALWAYS_AND_EXCLUDE};
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabbedPanelHoverPolicyProperty.java b/src/net/infonode/tabbedpanel/TabbedPanelHoverPolicyProperty.java
new file mode 100644
index 0000000..4101fdd
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedPanelHoverPolicyProperty.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelHoverPolicyProperty.java,v 1.3 2005/02/16 11:28:15 jesper Exp $
+
+package net.infonode.tabbedpanel;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.types.EnumProperty;
+import net.infonode.properties.util.PropertyValueHandler;
+
+/**
+ * Property for TabbedPanelHoverPolicy
+ *
+ * @author johan
+ * @version $Revision: 1.3 $
+ * @see TabbedPanelHoverPolicy
+ * @since ITP 1.3.0
+ */
+public class TabbedPanelHoverPolicyProperty extends EnumProperty {
+  /**
+   * Constructs a TabbedPanelHoverPolicyProperty object
+   *
+   * @param group        property group
+   * @param name         property name
+   * @param description  property description
+   * @param valueStorage storage for property
+   */
+  public TabbedPanelHoverPolicyProperty(PropertyGroup group,
+                                        String name,
+                                        String description,
+                                        PropertyValueHandler valueStorage) {
+    super(group,
+          name,
+          TabbedPanelHoverPolicy.class,
+          description,
+          valueStorage,
+          TabbedPanelHoverPolicy.getHoverPolicies());
+  }
+
+  /**
+   * Gets the TabbedPanelHoverPolicy
+   *
+   * @param object storage object for property
+   * @return the TabbedPanelHoverPolicy
+   */
+  public TabbedPanelHoverPolicy get(Object object) {
+    return (TabbedPanelHoverPolicy) getValue(object);
+  }
+
+  /**
+   * Sets the TabbedPanelHoverPolicy
+   *
+   * @param object storage object for property
+   * @param policy the TabbedPanelHoverPolicy
+   */
+  public void set(Object object, TabbedPanelHoverPolicy policy) {
+    setValue(object, policy);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/TabbedPanelProperties.java b/src/net/infonode/tabbedpanel/TabbedPanelProperties.java
new file mode 100644
index 0000000..6f12a28
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedPanelProperties.java
@@ -0,0 +1,1253 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelProperties.java,v 1.60 2005/12/04 13:46:05 jesper Exp $
+
+package net.infonode.tabbedpanel;
+
+import net.infonode.gui.DynamicUIManager;
+import net.infonode.gui.DynamicUIManagerListener;
+import net.infonode.gui.border.HighlightBorder;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.gui.icon.button.ArrowIcon;
+import net.infonode.gui.icon.button.BorderIcon;
+import net.infonode.gui.icon.button.DropDownIcon;
+import net.infonode.properties.base.Property;
+import net.infonode.properties.gui.util.ButtonProperties;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.*;
+import net.infonode.tabbedpanel.border.OpenContentBorder;
+import net.infonode.tabbedpanel.border.TabAreaLineBorder;
+import net.infonode.util.ArrayUtil;
+import net.infonode.util.Direction;
+
+import javax.swing.border.CompoundBorder;
+import java.awt.*;
+import java.awt.event.KeyEvent;
+import java.util.HashMap;
+
+/**
+ * TabbedPanelProperties holds all properties for a {@link TabbedPanel}. A
+ * TabbedPanelProperties object contains separate property objects for the
+ * content area, the tab area, the tab area components and the buttons of
+ * the TabbedPanel.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.60 $
+ * @see TabbedPanel
+ * @see #getContentPanelProperties
+ * @see #getTabAreaProperties
+ * @see #getTabAreaComponentsProperties
+ * @see #getButtonProperties
+ */
+public class TabbedPanelProperties extends PropertyMapContainer {
+  /**
+   * A property group for all properties in TabbedPanelProperties
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Tabbed Panel Properties",
+                                                                         "Properties for the TabbedPanel class.");
+
+  /**
+   * Tab reorder property
+   *
+   * @see #setTabReorderEnabled
+   * @see #getTabReorderEnabled
+   */
+  public static final BooleanProperty TAB_REORDER_ENABLED = new BooleanProperty(PROPERTIES,
+                                                                                "Tab Reorder Enabled",
+                                                                                "Tab reorder enabled or disabled",
+                                                                                PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Abort drag key code property
+   *
+   * @see #setAbortDragKey
+   * @see #getAbortDragKey
+   */
+  public static final IntegerProperty ABORT_DRAG_KEY = IntegerProperty.createPositive(PROPERTIES,
+                                                                                      "Abort Drag Key Code",
+                                                                                      "Key code for aborting drag",
+                                                                                      3,
+                                                                                      PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tab layout property
+   *
+   * @see #setTabLayoutPolicy
+   * @see #getTabLayoutPolicy
+   */
+  public static final TabLayoutPolicyProperty TAB_LAYOUT_POLICY = new TabLayoutPolicyProperty(PROPERTIES,
+                                                                                              "Layout Policy",
+                                                                                              "Tab layout in tab area",
+                                                                                              PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tab drop down list visible property
+   *
+   * @see #setTabDropDownListVisiblePolicy
+   * @see #getTabDropDownListVisiblePolicy
+   * @since ITP 1.1.0
+   */
+  public static final TabDropDownListVisiblePolicyProperty TAB_DROP_DOWN_LIST_VISIBLE_POLICY = new TabDropDownListVisiblePolicyProperty(
+      PROPERTIES,
+      "Tab Drop Down List Visible Policy",
+      "Determins when a drop down list with tabs should be visible in the tab area",
+      PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tab select trigger
+   *
+   * @see #setTabSelectTrigger
+   * @see #getTabSelectTrigger
+   * @since ITP 1.1.0
+   */
+  public static final TabSelectTriggerProperty TAB_SELECT_TRIGGER = new TabSelectTriggerProperty(PROPERTIES,
+                                                                                                 "Tab Select Trigger",
+                                                                                                 "Determins when a tab should be selected",
+                                                                                                 PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tab scrolling offset property
+   *
+   * @see #setTabScrollingOffset
+   * @see #getTabScrollingOffset
+   */
+  public static final IntegerProperty TAB_SCROLLING_OFFSET = IntegerProperty.createPositive(PROPERTIES,
+                                                                                            "Scroll Offset",
+                                                                                            "Number of pixels to be shown for the last scrolled tab",
+                                                                                            3,
+                                                                                            PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Ensure selected visible property
+   *
+   * @see #setEnsureSelectedTabVisible
+   * @see #getEnsureSelectedTabVisible
+   */
+  public static final BooleanProperty ENSURE_SELECTED_VISIBLE = new BooleanProperty(PROPERTIES, "Ensure Selected Visible", "Upon select, the selected tab will be scrolled into the visible area.",
+                                                                                    PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tab area orientation property
+   *
+   * @see #setTabAreaOrientation
+   * @see #getTabAreaOrientation
+   */
+  public static final DirectionProperty TAB_AREA_ORIENTATION = new DirectionProperty(PROPERTIES, "Tab Area Orientation", "Tab area's orientation relative to the content area.",
+                                                                                     PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tab spacing property
+   *
+   * @see #setTabSpacing
+   * @see #getTabSpacing
+   */
+  public static final IntegerProperty TAB_SPACING = new IntegerProperty(PROPERTIES,
+                                                                        "Tab Spacing",
+                                                                        "Number of pixels between tabs in tab area. A negative value will result in tab overlapping.",
+                                                                        Integer.MIN_VALUE,
+                                                                        Integer.MAX_VALUE,
+                                                                        3,
+                                                                        PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tab depth order.
+   *
+   * @see #setAutoSelectTab
+   * @see #getAutoSelectTab
+   * @since ITP 1.2.0
+   */
+  public static final TabDepthOrderPolicyProperty TAB_DEPTH_ORDER = new TabDepthOrderPolicyProperty(PROPERTIES,
+                                                                                                    "Tab Depth Order",
+                                                                                                    "Tabs will overlap when tab spacing is negative. Depth order tells if first tab should be the top most and the other tabs in descending order or if the first tab should be bottom most and the other tabs in ascending order.",
+                                                                                                    PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Auto select tab property
+   *
+   * @see #setAutoSelectTab
+   * @see #getAutoSelectTab
+   */
+  public static final BooleanProperty AUTO_SELECT_TAB = new BooleanProperty(PROPERTIES,
+                                                                            "Auto Select Tab",
+                                                                            "When enabled the first tab that i added will be selected automatically. "
+                                                                            +
+                                                                            "If the selected tab is removed then the tab next to the removed tab will be selected automatically.",
+                                                                            PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * If true the tab pressed with the mouse will be highlighted, otherwise it
+   * remains unchanged.
+   *
+   * @see #setHighlightPressedTab
+   * @see #getHighlightPressedTab
+   */
+  public static final BooleanProperty HIGHLIGHT_PRESSED_TAB = new BooleanProperty(PROPERTIES,
+                                                                                  "Highlight Pressed Tab",
+                                                                                  "If true the tab pressed with the mouse will be highlighted, otherwise it remains unchanged.",
+                                                                                  PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tab deselectable property
+   *
+   * @see #setTabDeselectable
+   * @see #getTabDeselectable
+   */
+  public static final BooleanProperty TAB_DESELECTABLE = new BooleanProperty(PROPERTIES, "Tab Deselectable", "When enabled the selected tab can be deselected by clicking on it.",
+                                                                             PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Content area properties
+   *
+   * @see #getContentPanelProperties
+   */
+  public static final PropertyMapProperty CONTENT_PANEL_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                             "Content Panel Properties",
+                                                                                             "Content panel properties.",
+                                                                                             TabbedPanelContentPanelProperties.PROPERTIES);
+
+  /**
+   * Tab area properties
+   *
+   * @see #getTabAreaProperties
+   */
+  public static final PropertyMapProperty TAB_AREA_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                        "Tab Area Properties",
+                                                                                        "Tab area properties.",
+                                                                                        TabAreaProperties.PROPERTIES);
+
+  /**
+   * Tab area components properties
+   *
+   * @see #getTabAreaComponentsProperties
+   * @since ITP 1.1.0
+   */
+  public static final PropertyMapProperty TAB_AREA_COMPONENTS_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                                   "Tab Area Components Properties",
+                                                                                                   "Tab area components properties.",
+                                                                                                   TabAreaComponentsProperties.PROPERTIES);
+
+  /**
+   * Button properties
+   *
+   * @see #getButtonProperties
+   * @since ITP 1.3.0
+   */
+  public static final PropertyMapProperty BUTTON_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                      "Tabbed Panel Button Properties",
+                                                                                      "Tabbed panel button properties.",
+                                                                                      TabbedPanelButtonProperties.PROPERTIES);
+
+  /**
+   * Shadow enabled property
+   *
+   * @see #setShadowEnabled
+   * @see #getShadowEnabled
+   */
+  public static final BooleanProperty SHADOW_ENABLED = new BooleanProperty(PROPERTIES,
+                                                                           "Shadow Enabled",
+                                                                           "Indicates that a shadow is painted for the selected tab and the content panel.\n"
+                                                                           +
+                                                                           "The shadow is partially painted using alpha transparency which can be slow on some systems.",
+                                                                           PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Hover listener property
+   *
+   * @see #setHoverListener
+   * @see #getHoverListener
+   * @since ITP 1.3.0
+   */
+  public static final HoverListenerProperty HOVER_LISTENER = new HoverListenerProperty(PROPERTIES,
+                                                                                       "Hover Listener",
+                                                                                       "Hover Listener to be used for tracking mouse hovering over the tabbed panel.",
+                                                                                       PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tabbed panel hover policy.
+   *
+   * @see #setHoverPolicy
+   * @see #getHoverPolicy
+   * @since ITP 1.3.0
+   */
+  public static final TabbedPanelHoverPolicyProperty HOVER_POLICY = new TabbedPanelHoverPolicyProperty(PROPERTIES,
+                                                                                                       "Hover Policy",
+                                                                                                       "Policy for when the Tabbed Panel is considerd hovered by the mouse.",
+                                                                                                       PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Paint a shadow for the tab area. If this property is set to false a
+   * shadow is painted for the highlighted tab and the tab area components
+   * panel.
+   *
+   * @see #setPaintTabAreaShadow(boolean)
+   * @see #getPaintTabAreaShadow()
+   */
+  public static final BooleanProperty PAINT_TAB_AREA_SHADOW = new BooleanProperty(PROPERTIES,
+                                                                                  "Paint Tab Area Shadow",
+                                                                                  "Paint a shadow for the tab area. If this property is set to false a shadow is painted for " +
+                                                                                  "the highlighted tab and the tab area components panel.",
+                                                                                  PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Shadow size property
+   *
+   * @see #setShadowSize
+   * @see #getShadowSize
+   */
+  public static final IntegerProperty SHADOW_SIZE = IntegerProperty.createPositive(PROPERTIES,
+                                                                                   "Shadow Size",
+                                                                                   "The size of the tab shadow.",
+                                                                                   2,
+                                                                                   PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Shadow blend area size property
+   *
+   * @see #setShadowBlendAreaSize
+   * @see #getShadowBlendAreaSize
+   */
+  public static final IntegerProperty SHADOW_BLEND_AREA_SIZE = IntegerProperty.createPositive(PROPERTIES,
+                                                                                              "Shadow Blend Size",
+                                                                                              "The size of the tab shadow blend area.",
+                                                                                              2,
+                                                                                              PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Shadow color property
+   *
+   * @see #setShadowColor
+   * @see #getShadowColor
+   */
+  public static final ColorProperty SHADOW_COLOR = new ColorProperty(PROPERTIES,
+                                                                     "Shadow Color",
+                                                                     "The color of the shadow.",
+                                                                     PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Shadow strength property
+   *
+   * @see #setShadowStrength
+   * @see #getShadowStrength
+   */
+  public static final FloatProperty SHADOW_STRENGTH = new FloatProperty(PROPERTIES,
+                                                                        "Shadow Strength",
+                                                                        "The strength of the shadow. 0 means the shadow color is the same as the backgound color, "
+                                                                        + "1 means the shadow color is '" +
+                                                                        SHADOW_COLOR +
+                                                                        "'.",
+                                                                        PropertyMapValueHandler.INSTANCE,
+                                                                        0,
+                                                                        1);
+
+  /**
+   * Array with all properties that controls the functional behavior
+   */
+  public static final Property[] FUNCTIONAL_PROPERTIES = {TAB_REORDER_ENABLED,
+                                                          ABORT_DRAG_KEY,
+                                                          TAB_LAYOUT_POLICY,
+                                                          ENSURE_SELECTED_VISIBLE,
+                                                          AUTO_SELECT_TAB,
+                                                          TAB_DESELECTABLE,
+                                                          TAB_SELECT_TRIGGER,
+                                                          HOVER_POLICY};
+
+  /**
+   * Array with all properties that controls the shadow
+   */
+  public static final Property[] SHADOW_PROPERTIES = {SHADOW_ENABLED,
+                                                      SHADOW_SIZE,
+                                                      SHADOW_BLEND_AREA_SIZE,
+                                                      SHADOW_COLOR,
+                                                      SHADOW_STRENGTH};
+
+  /**
+   * Array with all properties that controls the visual apperance except for
+   * shadow
+   */
+  public static final Property[] TABS_VISUAL_PROPERTIES = {TAB_SCROLLING_OFFSET,
+                                                           TAB_SPACING,
+                                                           TAB_AREA_ORIENTATION,
+                                                           TAB_AREA_PROPERTIES,
+                                                           TAB_AREA_COMPONENTS_PROPERTIES,
+                                                           TAB_LAYOUT_POLICY,
+                                                           CONTENT_PANEL_PROPERTIES,
+                                                           TAB_DROP_DOWN_LIST_VISIBLE_POLICY};
+
+  /**
+   * Array with all properties that controls the visual apperance including
+   * shadow
+   */
+  public static final Property[] VISUAL_PROPERTIES = (Property[]) ArrayUtil.append(TABS_VISUAL_PROPERTIES,
+                                                                                   SHADOW_PROPERTIES,
+                                                                                   new Property[TABS_VISUAL_PROPERTIES.length +
+                                                                                                SHADOW_PROPERTIES.length]);
+
+  private static final TabbedPanelProperties DEFAULT_PROPERTIES = new TabbedPanelProperties(PROPERTIES.getDefaultMap());
+
+  static {
+    DynamicUIManager.getInstance().addListener(new DynamicUIManagerListener() {
+      public void lookAndFeelChanged() {
+        updateVisualProperties();
+      }
+
+      public void propertiesChanged() {
+        updateVisualProperties();
+      }
+
+      public void propertiesChanging() {
+      }
+
+      public void lookAndFeelChanging() {
+      }
+    });
+
+    updateVisualProperties();
+    updateFunctionalProperties();
+  }
+
+  /**
+   * Creates a properties object with default properties based on the current
+   * look and feel
+   *
+   * @return properties object
+   */
+  public static TabbedPanelProperties getDefaultProperties() {
+    return new TabbedPanelProperties(DEFAULT_PROPERTIES);
+  }
+
+  private static void updateVisualProperties() {
+    PropertyMapManager.runBatch(new Runnable() {
+      public void run() {
+        DEFAULT_PROPERTIES.getContentPanelProperties().getComponentProperties().setBorder(
+            new OpenContentBorder(TabbedUIDefaults.getDarkShadow(), TabbedUIDefaults.getHighlight()))
+            .setInsets(TabbedUIDefaults.getContentAreaInsets())
+            .setBackgroundColor(TabbedUIDefaults.getContentAreaBackground());
+        DEFAULT_PROPERTIES.getContentPanelProperties().getShapedPanelProperties().setOpaque(true);
+
+        DEFAULT_PROPERTIES.getTabAreaComponentsProperties().setStretchEnabled(false).getComponentProperties()
+            .setBorder(new CompoundBorder(new TabAreaLineBorder(TabbedUIDefaults.getDarkShadow()),
+                                          new HighlightBorder(false, TabbedUIDefaults.getHighlight())))
+            .setBackgroundColor(TabbedUIDefaults.getContentAreaBackground());
+        DEFAULT_PROPERTIES.getTabAreaComponentsProperties().getShapedPanelProperties().setOpaque(true);
+
+        DEFAULT_PROPERTIES.getTabAreaProperties().getShapedPanelProperties().setOpaque(false);
+      }
+    });
+  }
+
+  private static void updateFunctionalProperties() {
+    DEFAULT_PROPERTIES.setTabReorderEnabled(false)
+        .setAbortDragKey(KeyEvent.VK_ESCAPE)
+        .setTabLayoutPolicy(TabLayoutPolicy.SCROLLING)
+        .setTabDropDownListVisiblePolicy(TabDropDownListVisiblePolicy.NEVER)
+        .setTabSelectTrigger(TabSelectTrigger.MOUSE_PRESS)
+        .setTabScrollingOffset(10).setTabSpacing(-1)
+        .setTabDepthOrderPolicy(TabDepthOrderPolicy.DESCENDING)
+        .setEnsureSelectedTabVisible(false)
+        .setTabAreaOrientation(Direction.UP)
+        .setAutoSelectTab(true)
+        .setHighlightPressedTab(true)
+
+        .setHoverPolicy(TabbedPanelHoverPolicy.NO_HOVERED_CHILD)
+
+        .setShadowEnabled(false)
+        .setShadowSize(3)
+        .setShadowBlendAreaSize(2)
+        .setShadowColor(Color.BLACK)
+        .setShadowStrength(0.4F);
+
+    DEFAULT_PROPERTIES.getTabAreaProperties().setTabAreaVisiblePolicy(TabAreaVisiblePolicy.ALWAYS);
+
+    HashMap buttonMap = new HashMap();
+    buttonMap.put(Direction.DOWN, DEFAULT_PROPERTIES.getButtonProperties().getScrollDownButtonProperties());
+    buttonMap.put(Direction.UP, DEFAULT_PROPERTIES.getButtonProperties().getScrollUpButtonProperties());
+    buttonMap.put(Direction.RIGHT, DEFAULT_PROPERTIES.getButtonProperties().getScrollRightButtonProperties());
+    buttonMap.put(Direction.LEFT, DEFAULT_PROPERTIES.getButtonProperties().getScrollLeftButtonProperties());
+
+    int iconSize = TabbedUIDefaults.getButtonIconSize();
+
+    Direction[] directions = Direction.getDirections();
+    for (int i = 0; i < directions.length; i++) {
+      ArrowIcon disabledIcon = new ArrowIcon(iconSize - 2, directions[i], false);
+      disabledIcon.setShadowEnabled(false);
+
+      ((ButtonProperties) buttonMap.get(directions[i]))
+          .setFactory(TabbedPanelDefaultButtonFactories.getScrollDownButtonFactory())
+          .setIcon(new ArrowIcon(iconSize, directions[i]))
+          .setDisabledIcon(new BorderIcon(disabledIcon, 1));
+    }
+
+    DEFAULT_PROPERTIES.getButtonProperties().getTabDropDownListButtonProperties()
+        .setFactory(TabbedPanelDefaultButtonFactories.getScrollDownButtonFactory())
+        .setIcon(new DropDownIcon(Color.black, TabbedUIDefaults.getButtonIconSize(), Direction.DOWN))
+        .setDisabledIcon(null);
+  }
+
+  /**
+   * Constructs an empty TabbedPanelProperties object
+   */
+  public TabbedPanelProperties() {
+    super(PROPERTIES);
+  }
+
+  /**
+   * Constructs a TabbedPanelProperties map with the given map as property
+   * storage
+   *
+   * @param map map to store properties in
+   */
+  public TabbedPanelProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Constructs a TabbedPanelProperties object that inherits its properties
+   * from the given TabbedPanelProperties object
+   *
+   * @param inheritFrom TabbedPanelProperties object to inherit properties from
+   */
+  public TabbedPanelProperties(TabbedPanelProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param superObject the object from which to inherit property values
+   * @return this
+   */
+  public TabbedPanelProperties addSuperObject(TabbedPanelProperties superObject) {
+    getMap().addSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   */
+  public TabbedPanelProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes the given super object.
+   *
+   * @param superObject super object to remove
+   * @return this
+   * @since ITP 1.3.0
+   */
+  public TabbedPanelProperties removeSuperObject(TabbedPanelProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Replaces the given super objects.
+   *
+   * @param oldSuperObject old super object
+   * @param newSuperObject new super object
+   * @return this
+   * @since ITP 1.4.0
+   */
+  public TabbedPanelProperties replaceSuperObject(TabbedPanelProperties oldSuperObject, TabbedPanelProperties newSuperObject) {
+    getMap().replaceSuperMap(oldSuperObject.getMap(), newSuperObject.getMap());
+    return this;
+  }
+
+  /**
+   * <p>
+   * Sets the shadow strength. 0 means the shadow color is the same as the
+   * backgound color and 1 means the shadow color is the same as shadow color.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This property will only have effect if shadow is
+   * enabled.
+   * </p>
+   *
+   * @param strength the strength between 0 and 1
+   * @return this TabbedPanelProperties
+   * @see #setShadowColor
+   * @see #setShadowEnabled
+   */
+  public TabbedPanelProperties setShadowStrength(float strength) {
+    SHADOW_STRENGTH.set(getMap(), strength);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Sets the shadow blend area size, i.e. number of pixels for the shadow
+   * color fading.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This property will only have effect if shadow is
+   * enabled.
+   * </p>
+   *
+   * @param size the shadow blend area size in pixels
+   * @return this TabbedPanelProperties
+   * @see #setShadowEnabled
+   */
+  public TabbedPanelProperties setShadowBlendAreaSize(int size) {
+    SHADOW_BLEND_AREA_SIZE.set(getMap(), size);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Sets the shadow size, i.e. the width/height of the shadow in pixels.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This property will only have effect if shadow is
+   * enabled.
+   * </p>
+   *
+   * @param size shadow size in pixels
+   * @return this TabbedPanelProperties
+   * @see #setShadowEnabled
+   */
+  public TabbedPanelProperties setShadowSize(int size) {
+    SHADOW_SIZE.set(getMap(), size);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Sets the shadow color.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This property will only have effect if shadow is
+   * enabled.
+   * </p>
+   *
+   * @param color the shadow color
+   * @return this TabbedPanelProperties
+   * @see #setShadowEnabled
+   */
+  public TabbedPanelProperties setShadowColor(Color color) {
+    SHADOW_COLOR.set(getMap(), color);
+    return this;
+  }
+
+  /**
+   * Sets shadow enabled
+   *
+   * @param value true for enabled, otherwise false
+   * @return this TabbedPanelProperties
+   */
+  public TabbedPanelProperties setShadowEnabled(boolean value) {
+    SHADOW_ENABLED.set(getMap(), value);
+    return this;
+  }
+
+  /**
+   * Sets if automatic selection of a tab is enabled. Automatic selection
+   * means that if no tab is selected and a tab is added to the TabbedPanel,
+   * then the added tab will automatically be selected. If a selected tab is
+   * removed from the TabbedPanel then the tab next to the selected tab will
+   * automatically be selected.
+   *
+   * @param value true for automactic selection, otherwise false
+   * @return this TabbedPanelProperties
+   */
+  public TabbedPanelProperties setAutoSelectTab(boolean value) {
+    AUTO_SELECT_TAB.set(getMap(), value);
+    return this;
+  }
+
+  /**
+   * Sets if tab is deselectable. This means that if the selected tab is
+   * clicked then the selected tab will be deselected. Clicking it again will
+   * select the tab again.
+   *
+   * @param value true for deselectable, otherwise false
+   * @return this TabbedPanelProperties
+   */
+  public TabbedPanelProperties setTabDeselectable(boolean value) {
+    TAB_DESELECTABLE.set(getMap(), value);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Sets if a tab should be made visible if it is selected, i.e. if scrolling
+   * is enabled, a tab will be scrolled into the visible part of the tab area
+   * when it becomes selected.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This will only have effect if scolling is
+   * enabled.
+   * </p>
+   *
+   * @param value true for selected visible, otherwise false
+   * @return this TabbedPanelProperties
+   * @see #setTabLayoutPolicy
+   */
+  public TabbedPanelProperties setEnsureSelectedTabVisible(boolean value) {
+    ENSURE_SELECTED_VISIBLE.set(getMap(), value);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Sets number of pixels to be shown for the scrolled out tab next to the
+   * first visible tab.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This will only have effect if scolling is
+   * enabled.
+   * </p>
+   *
+   * @param offset number of pixels
+   * @return this TabbedPanelProperties
+   * @see #setTabLayoutPolicy
+   */
+  public TabbedPanelProperties setTabScrollingOffset(int offset) {
+    TAB_SCROLLING_OFFSET.set(getMap(), offset);
+    return this;
+  }
+
+  /**
+   * Sets if the tabs can be reordered using the mouse
+   *
+   * @param enabled true for enabled, otherwise disabled
+   * @return this TabbedPanelProperties
+   */
+  public TabbedPanelProperties setTabReorderEnabled(boolean enabled) {
+    TAB_REORDER_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Set to true if the tab pressed with the mouse should be highlighted,
+   * otherwise it's not changed.
+   *
+   * @param highlightEnabled true if the tab pressed with the mouse should be highlighted
+   * @return this
+   */
+  public TabbedPanelProperties setHighlightPressedTab(boolean highlightEnabled) {
+    HIGHLIGHT_PRESSED_TAB.set(getMap(), highlightEnabled);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Sets the key code for aborting a tab drag or reorder operation.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> The right mouse button can also be used to abort
+   * the operation.
+   * </p>
+   *
+   * @param keyCode key code
+   * @return this TabbedPanelProperties
+   */
+  public TabbedPanelProperties setAbortDragKey(int keyCode) {
+    ABORT_DRAG_KEY.set(getMap(), keyCode);
+    return this;
+  }
+
+  /**
+   * Sets the tab layout policy for the tab area, i.e. how the line of tabs
+   * should be laid out
+   *
+   * @param policy the tab area layout policy
+   * @return this TabbedPanelProperties
+   */
+  public TabbedPanelProperties setTabLayoutPolicy(TabLayoutPolicy policy) {
+    TAB_LAYOUT_POLICY.set(getMap(), policy);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Sets the tab drop down list visible policy, i.e. when a drop down list
+   * with the tabs should be visible
+   * </p>
+   *
+   * <p>
+   * The drop down list will show an icon for the tab if the tab implements
+   * the {@link net.infonode.gui.icon.IconProvider} and the text will be retrieved by calling
+   * toString() on the tab.
+   * </p>
+   *
+   * @param policy the tab drop down list visible policy
+   * @return this TabbedPanelProperties
+   * @since ITP 1.1.0
+   */
+  public TabbedPanelProperties setTabDropDownListVisiblePolicy(TabDropDownListVisiblePolicy policy) {
+    TAB_DROP_DOWN_LIST_VISIBLE_POLICY.set(getMap(), policy);
+    return this;
+  }
+
+  /**
+   * Sets the tab select trigger, i.e. what triggers a tab selection
+   *
+   * @param trigger the tab select trigger
+   * @return this TabbedPanelProperties
+   * @since ITP 1.1.0
+   */
+  public TabbedPanelProperties setTabSelectTrigger(TabSelectTrigger trigger) {
+    TAB_SELECT_TRIGGER.set(getMap(), trigger);
+    return this;
+  }
+
+  /**
+   * Sets the tab area orientation, i.e. if the tab area should be placed up,
+   * down, left or right of the content area.
+   *
+   * @param direction the orientation
+   * @return this TabbedPanelProperties
+   */
+  public TabbedPanelProperties setTabAreaOrientation(Direction direction) {
+    TAB_AREA_ORIENTATION.set(getMap(), direction);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Sets the tab spacing, i.e. number of pixels between the tabs in the tab
+   * area.
+   * </p>
+   *
+   * <p>
+   * This can be a negative value i.e. tabs will be overlapping each other. The
+   * depth order can be controlled with the property TAB_DEPTH_ORDER.
+   * </p>
+   *
+   * @param value number of pixels. A negative value reuslts in tabs
+   *              overlapping each other with the number of pixels.
+   * @return this TabbedPanelProperties
+   * @see #setTabDepthOrderPolicy
+   */
+  public TabbedPanelProperties setTabSpacing(int value) {
+    TAB_SPACING.set(getMap(), value);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Sets the tab depth order policy to be used when tabs are overlapping i.e.
+   * negative tab spacing.
+   * </p>
+   *
+   * <p>
+   * If the depth order is descending, the first tab will be the top most and
+   * the last tab the bottom most. If the depth order is ascending, then the
+   * first tab will be the bottom most and the last tab the top most. Note that
+   * if a tab is highlighted, it will always be shown as the top most tab.
+   * </p>
+   *
+   * @param policy the tab depth order policy
+   * @return this TabbedPanelProperties
+   * @see #setTabSpacing
+   * @since ITP 1.2.0
+   */
+  public TabbedPanelProperties setTabDepthOrderPolicy(TabDepthOrderPolicy policy) {
+    TAB_DEPTH_ORDER.set(getMap(), policy);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Gets the shadow strength. 0 means the shadow color is the same as the
+   * backgound color and 1 means the shadow color is the same as shadow color.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This property will only have effect if shadow is
+   * enabled.
+   * </p>
+   *
+   * @return the shadow strength between 0 and 1
+   * @see #getShadowColor
+   * @see #getShadowEnabled
+   */
+  public float getShadowStrength() {
+    return SHADOW_STRENGTH.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Gets the shadow blend area size, i.e. number of pixels for the shadow
+   * color fading.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This property will only have effect if shadow is
+   * enabled.
+   * </p>
+   *
+   * @return the shadow blend area size in pixels
+   * @see #getShadowEnabled
+   */
+  public int getShadowBlendAreaSize() {
+    return SHADOW_BLEND_AREA_SIZE.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Gets the shadow size, i.e. the width/height of the shadow in pixels.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This property will only have effect if shadow is
+   * enabled.
+   * </p>
+   *
+   * @return shadow size in pixels
+   * @see #getShadowEnabled
+   */
+  public int getShadowSize() {
+    return SHADOW_SIZE.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Gets the shadow color.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This property will only have effect if shadow is
+   * enabled.
+   * </p>
+   *
+   * @return the shadow color
+   * @see #getShadowEnabled
+   */
+  public Color getShadowColor() {
+    return SHADOW_COLOR.get(getMap());
+  }
+
+  /**
+   * Gets shadow enabled
+   *
+   * @return true if shadow is enabled, otherwise false
+   */
+  public boolean getShadowEnabled() {
+    return SHADOW_ENABLED.get(getMap());
+  }
+
+  /**
+   * Gets if automatic selection of a tab is enabled. Automatic selection
+   * means that if no tab is selected and a tab is added to the TabbedPanel,
+   * then the added tab will automatically be selected. If a selected tab is
+   * removed from the TabbedPanel then the tab next to the selected tab will
+   * automatically be selected.
+   *
+   * @return true if automactic selection, otherwise false
+   */
+  public boolean getAutoSelectTab() {
+    return AUTO_SELECT_TAB.get(getMap());
+  }
+
+  /**
+   * Gets if the tab pressed with the mouse will be highlighted.
+   *
+   * @return true if the tab pressed with the mouse will be highlighted
+   */
+  public boolean getHighlightPressedTab() {
+    return HIGHLIGHT_PRESSED_TAB.get(getMap());
+  }
+
+  /**
+   * Gets if tab is deselectable. This means that if the selected tab is
+   * clicked then the selected tab will be deselected. Clicking it again will
+   * select the tab again.
+   *
+   * @return true if deselectable, otherwise false
+   */
+  public boolean getTabDeselectable() {
+    return TAB_DESELECTABLE.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Gets if a tab should be made visible if it is selected, i.e. if scrolling
+   * is enabled, a tab will be scrolled into the visible part of the tab area
+   * when it becomes selected.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This will only have effect if scolling is
+   * enabled.
+   * </p>
+   *
+   * @return true if selected visible should be made visible, otherwise false
+   * @see #getTabLayoutPolicy
+   */
+  public boolean getEnsureSelectedTabVisible() {
+    return ENSURE_SELECTED_VISIBLE.get(getMap());
+  }
+
+  /**
+   * Returns true if a shadow is painted for the tab area, false if a shadow
+   * is painted for the highlighted tab and the tab area components panel.
+   *
+   * @return true if a shadow is painted for the tab area, false if a shadow
+   *         is painted for the highlighted tab and the tab area components
+   *         panel
+   * @since ITP 1.1.0
+   */
+  public boolean getPaintTabAreaShadow() {
+    return PAINT_TAB_AREA_SHADOW.get(getMap());
+  }
+
+  /**
+   * Set to true if a shadow should be painted for the tab area, false if a
+   * shadow should be painted for the highlighted tab and the tab area
+   * components panel.
+   *
+   * @param paintShadow true if a shadow should be painted for the tab area, false if
+   *                    a shadow should be painted for the highlighted tab and the tab
+   *                    area components panel
+   * @return this
+   * @since ITP 1.1.0
+   */
+  public TabbedPanelProperties setPaintTabAreaShadow(boolean paintShadow) {
+    PAINT_TAB_AREA_SHADOW.set(getMap(), paintShadow);
+    return this;
+  }
+
+  /**
+   * <p>
+   * Gets number of pixels to be shown for the last scrolled tab.
+   * </p>
+   * <p>
+   * <strong>Note: </strong> This will only have effect if scolling is
+   * enabled.
+   * </p>
+   *
+   * @return number of pixels
+   * @see #getTabLayoutPolicy
+   */
+  public int getTabScrollingOffset() {
+    return TAB_SCROLLING_OFFSET.get(getMap());
+  }
+
+  /**
+   * Gets if the tabs can be reorder using the mouse.
+   *
+   * @return true if enabled, otherwise disabled
+   */
+  public boolean getTabReorderEnabled() {
+    return TAB_REORDER_ENABLED.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Gets the key code for aborting a tab drag or reorder operation.
+   * </p>
+   * <p>
+   * Note that the right mouse button can also be used to abort the operation.
+   * </p>
+   *
+   * @return the key code
+   */
+  public int getAbortDragKey() {
+    return ABORT_DRAG_KEY.get(getMap());
+  }
+
+  /**
+   * Gets the tab layout policy for the tab area, i.e. how the line of tabs
+   * should be laid out
+   *
+   * @return the tab area layout policy
+   */
+  public TabLayoutPolicy getTabLayoutPolicy() {
+    return TAB_LAYOUT_POLICY.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Gets the tab drop down list visible policy, i.e. when a drop down list
+   * with the tabs should be visible.
+   * </p>
+   *
+   * <p>
+   * The drop down list will show an icon for the tab if the tab implements
+   * the {@link net.infonode.gui.icon.IconProvider} and the text will be retrieved by calling
+   * toString() on the tab.
+   * </p>
+   *
+   * @return the tab drop down list visible policy
+   * @since ITP 1.1.0
+   */
+  public TabDropDownListVisiblePolicy getTabDropDownListVisiblePolicy() {
+    return TAB_DROP_DOWN_LIST_VISIBLE_POLICY.get(getMap());
+  }
+
+  /**
+   * Gets the tab select trigger, i.e. what triggers a tab selection
+   *
+   * @return the tab select trigger
+   * @since ITP 1.1.0
+   */
+  public TabSelectTrigger getTabSelectTrigger() {
+    return TAB_SELECT_TRIGGER.get(getMap());
+  }
+
+  /**
+   * Gets the tab area orientation, i.e. if the tab area should be placed up,
+   * down, left or right of the content area
+   *
+   * @return the orientation
+   */
+  public Direction getTabAreaOrientation() {
+    return TAB_AREA_ORIENTATION.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Gets the tab spacing, i.e. number of pixels between the tabs in the tab
+   * area.
+   * </p>
+   *
+   * <p>
+   * This can be a negative value i.e. tabs will be overlapping each other. The
+   * depth order can be controlled with the property TAB_DEPTH_ORDER.
+   * </p>
+   *
+   * @return number of pixels, can be negative i.e. tabs will be overlapping
+   * @see #getTabDepthOrderPolicy
+   */
+  public int getTabSpacing() {
+    return TAB_SPACING.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Gets the tab depth order policy to be used when tabs are overlapping i.e.
+   * negative tab spacing.
+   * </p>
+   *
+   * <p>
+   * If the depth order is descending, the first tab will be the top most and
+   * the last tab the bottom most. If the depth order is ascending, then the
+   * first tab will be the bottom most and the last tab the top most. Note that
+   * if a tab is highlighted, it will always be shown as the top most tab.
+   * </p>
+   *
+   * @return the tab depth order policy
+   * @see #getTabSpacing
+   * @since ITP 1.2.0
+   */
+  public TabDepthOrderPolicy getTabDepthOrderPolicy() {
+    return TAB_DEPTH_ORDER.get(getMap());
+  }
+
+  /**
+   * <p>Sets the hover listener that will be triggered when the tabbed panel is hoverd by the mouse.</p>
+   *
+   * <p>The hovered tabbed panel will be the source of the hover event sent to the
+   * hover listener.</p>
+   *
+   * @param listener the hover listener
+   * @return this TabbedPanelProperties
+   * @since ITP 1.3.0
+   */
+  public TabbedPanelProperties setHoverListener(HoverListener listener) {
+    HOVER_LISTENER.set(getMap(), listener);
+    return this;
+  }
+
+  /**
+   * <p>Gets the hover listener that will be triggered when the tabbed panel is hovered by the mouse.</p>
+   *
+   * <p>The hovered tabbed panel will be the source of the hover event sent to the
+   * hover listener.</p>
+   *
+   * @return the hover listener
+   * @since ITP 1.3.0
+   */
+  public HoverListener getHoverListener() {
+    return HOVER_LISTENER.get(getMap());
+  }
+
+  /**
+   * <p>Sets the hover policy.</p>
+   *
+   * <p>The hover policy determines when the tabbed panel is considered hovered by the mouse and the
+   * hover listener is called. The default hover policy is NO_HOVERED_CHILD.</p>
+   *
+   * @param hoverPolicy the hover policy
+   * @return this TabbedPanelProperties
+   * @since ITP 1.3.0
+   */
+  public TabbedPanelProperties setHoverPolicy(TabbedPanelHoverPolicy hoverPolicy) {
+    HOVER_POLICY.set(getMap(), hoverPolicy);
+    return this;
+  }
+
+  /**
+   * <p>Gets the hover policy.</p>
+   *
+   * <p>The hover policy determines when the tabbed panel is considered hovered by the mouse and the
+   * hover listener is called. The default hover policy is NO_HOVERED_CHILD.</p>
+   *
+   * @return the hover policy
+   * @since ITP 1.3.0
+   */
+  public TabbedPanelHoverPolicy getHoverPolicy() {
+    return HOVER_POLICY.get(getMap());
+  }
+
+  /**
+   * Gets the properties getMap() with properties for the tabbed panel's
+   * content area
+   *
+   * @return the properties for the content area
+   */
+  public TabbedPanelContentPanelProperties getContentPanelProperties() {
+    return new TabbedPanelContentPanelProperties(CONTENT_PANEL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the properties getMap() with properties for the tabbed panel's tab
+   * area
+   *
+   * @return the properties for the tab area
+   */
+  public TabAreaProperties getTabAreaProperties() {
+    return new TabAreaProperties(TAB_AREA_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the properties getMap() with properties for the area in a tabbed
+   * panel's tab area where the tab area components are shown.
+   *
+   * @return the properties for the tab area components
+   * @since ITP 1.1.0
+   */
+  public TabAreaComponentsProperties getTabAreaComponentsProperties() {
+    return new TabAreaComponentsProperties(TAB_AREA_COMPONENTS_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the properties getMap() with properties for all the buttons in a
+   * tabbed panel.
+   *
+   * @return the properties for the buttons
+   * @since ITP 1.3.0
+   */
+  public TabbedPanelButtonProperties getButtonProperties() {
+    return new TabbedPanelButtonProperties(BUTTON_PROPERTIES.get(getMap()));
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/TabbedPanelReleaseInfo.java b/src/net/infonode/tabbedpanel/TabbedPanelReleaseInfo.java
new file mode 100644
index 0000000..a72b497
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedPanelReleaseInfo.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelReleaseInfo.java,v 1.8 2004/09/22 14:33:49 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.util.AntUtils;
+import net.infonode.util.ReleaseInfo;
+
+/**
+ * Tabbed Panel release information. Contains product name, vendor, build time
+ * and version info for the current release.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class TabbedPanelReleaseInfo {
+  private static ReleaseInfo productInfo =
+      new ReleaseInfo("InfoNode Tabbed Panel GPL",
+                      "NNL Technology AB",
+                      AntUtils.getBuildTime(1235486906703L),
+                      AntUtils.createProductVersion(1, 6, 1),
+                      "GNU General Public License, Version 2",
+                      "http://www.infonode.net");
+
+  private TabbedPanelReleaseInfo() {
+  }
+
+  /**
+   * Gets the release information
+   *
+   * @return release information
+   */
+  public static ReleaseInfo getReleaseInfo() {
+    return productInfo;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabbedUIDefaults.java b/src/net/infonode/tabbedpanel/TabbedUIDefaults.java
new file mode 100644
index 0000000..2197cbf
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedUIDefaults.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedUIDefaults.java,v 1.15 2005/08/29 12:18:41 johan Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.gui.UIManagerUtil;
+import net.infonode.util.ColorUtil;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * Methods for retrieving UI defaults for the current "Look and Feel" from the
+ * UIManager. The values are adapted to be used with classes in the TabbedPanel package.
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.15 $
+ */
+public class TabbedUIDefaults {
+  private static final int BUTTON_ICON_SIZE = 11;
+
+  private TabbedUIDefaults() {
+  }
+
+  /**
+   * Gets the content area background color
+   *
+   * @return a copy of the color
+   */
+  public static Color getContentAreaBackground() {
+    return UIManagerUtil.getColor("Panel.background", "control", Color.LIGHT_GRAY);
+  }
+
+  /**
+   * Gets the tab normal state foreground color
+   *
+   * @return a copy of the color
+   */
+  public static Color getNormalStateForeground() {
+    return UIManagerUtil.getColor("TabbedPane.foreground", "controlText", Color.BLACK);
+  }
+
+  /**
+   * Gets the tab normal state background color
+   *
+   * @return a copy of the color
+   */
+  public static Color getNormalStateBackground() {
+    return UIManagerUtil.getColor("TabbedPane.background", "control", Color.LIGHT_GRAY);
+  }
+
+  /**
+   * Gets the tab highlighted state foreground color
+   *
+   * @return a copy of the color
+   */
+  public static Color getHighlightedStateForeground() {
+    return getNormalStateForeground();
+  }
+
+  /**
+   * Gets the tab highlighted state backgound color
+   *
+   * @return a copy of the color
+   */
+  public static Color getHighlightedStateBackground() {
+    return UIManagerUtil.getColor("Panel.background", "control", Color.LIGHT_GRAY);
+  }
+
+  /**
+   * Gets the tab disabled state foreground color
+   *
+   * @return a copy of the color
+   */
+  public static Color getDisabledForeground() {
+    return UIManagerUtil.getColor("inactiveCaptionText", "controlText", Color.WHITE);
+  }
+
+  /**
+   * Gets the tab disabled state background color
+   *
+   * @return a copy of the color
+   */
+  public static Color getDisabledBackground() {
+    return ColorUtil.mult(getNormalStateBackground(), 0.9);
+  }
+
+  /**
+   * Gets the (border) dark shadow color
+   *
+   * @return a copy of the color
+   */
+  public static Color getDarkShadow() {
+    return UIManagerUtil.getColor("TabbedPane.darkShadow", "controlDkShadow", Color.BLACK);
+  }
+
+  /**
+   * Gets the (border) highlight color
+   *
+   * @return a copy of the color
+   */
+  public static Color getHighlight() {
+    return UIManagerUtil.getColor("TabbedPane.highlight", "controlHighlight", Color.WHITE);
+  }
+
+  /**
+   * Gets the font
+   *
+   * @return a copy of the font
+   */
+  public static Font getFont() {
+    return UIManagerUtil.getFont("TabbedPane.font");
+  }
+
+  /**
+   * Gets the icon text gap
+   *
+   * @return the icon text gap
+   */
+  public static int getIconTextGap() {
+    return UIManager.getInt("TabbedPane.textIconGap");
+  }
+
+  /**
+   * Gets the tab insets
+   *
+   * @return a copy of the insets
+   */
+  public static Insets getTabInsets() {
+    return UIManagerUtil.getInsets("TabbedPane.tabInsets", new Insets(2, 2, 0, 2));
+  }
+
+  /**
+   * Gets the insets for the content area
+   *
+   * @return a copy of the insets
+   */
+  public static Insets getContentAreaInsets() {
+    return UIManagerUtil.getInsets("TabbedPane.contentBorderInsets", new Insets(2, 2, 2, 2));
+  }
+
+  /**
+   * Gets the default icon size for buttons
+   *
+   * @return icon size in pixels
+   */
+  public static int getButtonIconSize() {
+    return BUTTON_ICON_SIZE;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/TabbedUtils.java b/src/net/infonode/tabbedpanel/TabbedUtils.java
new file mode 100644
index 0000000..4071b15
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/TabbedUtils.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedUtils.java,v 1.10 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel;
+
+import net.infonode.gui.hover.hoverable.HoverManager;
+
+import java.awt.*;
+
+/**
+ * Utility methods
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.10 $
+ */
+public class TabbedUtils {
+  private TabbedUtils() {
+  }
+
+  /**
+   * <p>Gets the tab for whom the given component is a child.</p>
+   *
+   * <p><strong>Note:</strong> This is not a method for retrieving the tab for a specific content
+   * component. This method is only useful for finding the Tab for components that
+   * have been added to a Tab.</p>
+   *
+   * @param c the component
+   * @return the tab or null if component is not a child of any tab
+   */
+  public static Tab getParentTab(Component c) {
+    while (c != null) {
+      if (c instanceof Tab)
+        return (Tab) c;
+      c = c.getParent();
+    }
+    return null;
+  }
+
+  /**
+   * Gets the tabbed panel for whom the given component is a child
+   *
+   * @param c the component
+   * @return the tabbed panel or null if component is not a child of any tabbed panel
+   */
+  public static TabbedPanel getParentTabbedPanel(Component c) {
+    while (c != null) {
+      if (c instanceof TabbedPanel)
+        return (TabbedPanel) c;
+      c = c.getParent();
+    }
+
+    return null;
+  }
+
+  /**
+   * Gets the TabbedPanelContentPanel for whom the given component is a child
+   *
+   * @param c the component
+   * @return the content panel or null if component is not a child of any
+   *         tabbed panel content panel
+   */
+  public static TabbedPanelContentPanel getParentTabbedPanelContentPanel(Component c) {
+    while (c != null) {
+      if (c instanceof TabbedPanelContentPanel)
+        return (TabbedPanelContentPanel) c;
+
+      c = c.getParent();
+    }
+
+    return null;
+  }
+
+  /**
+   * <p>
+   * Checks to see if hover is enabled i.e. if the AWTPermission "listenToAllAWTEvents" has been
+   * granted so that hover can be used.
+   * </p>
+   *
+   * <p>
+   * Note: This method is not meant to be used for permission checks, only as a convenience to
+   * check if hover is enabled or not.
+   * </p>
+   *
+   * @return true if hover is enabled, otherwise false
+   * @since ITP 1.3.0
+   */
+  public static boolean isHoverEnabled() {
+    return HoverManager.getInstance().isEventListeningActive();
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/border/GradientTabAreaBorder.java b/src/net/infonode/tabbedpanel/border/GradientTabAreaBorder.java
new file mode 100644
index 0000000..d37915b
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/border/GradientTabAreaBorder.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: GradientTabAreaBorder.java,v 1.16 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.tabbedpanel.border;
+
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.ColorProviderUtil;
+import net.infonode.gui.colorprovider.UIManagerColorProvider;
+import net.infonode.gui.componentpainter.GradientComponentPainter;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedUtils;
+
+import javax.swing.border.Border;
+import java.awt.*;
+import java.io.Serializable;
+
+/**
+ * Paints a gradient background for a tab area component. The background blends from one color on the component edge
+ * opposite to the tabbed panel content panel to the another color on the component edge closest to the tabbed panel
+ * content panel.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.16 $
+ * @since ITP 1.1.0
+ */
+public class GradientTabAreaBorder implements Border, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private GradientComponentPainter painter;
+
+  /**
+   * Creates a border where the color of the component edge closest to the tabben panel content panel will be the
+   * default control color.
+   *
+   * @param topColor the color of the component edge opposite to the tabbed panel content panel
+   */
+  public GradientTabAreaBorder(Color topColor) {
+    this(topColor, null);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param topColor    the color of the component edge opposite to the tabbed panel content panel
+   * @param bottomColor the color of the component edge closest to the tabbed panel content panel
+   */
+  public GradientTabAreaBorder(Color topColor, Color bottomColor) {
+    this(ColorProviderUtil.getColorProvider(topColor, UIManagerColorProvider.CONTROL_COLOR),
+         ColorProviderUtil.getColorProvider(bottomColor, UIManagerColorProvider.CONTROL_COLOR));
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param topColorProvider    provides the color of the component edge opposite to the tabbed panel content panel
+   * @param bottomColorProvider provides the color of the component edge closest to the tabbed panel content panel
+   */
+  public GradientTabAreaBorder(ColorProvider topColorProvider, ColorProvider bottomColorProvider) {
+    painter = new GradientComponentPainter(topColorProvider,
+                                           topColorProvider,
+                                           bottomColorProvider,
+                                           bottomColorProvider);
+  }
+
+  public boolean isBorderOpaque() {
+    return true;
+  }
+
+  public void paintBorder(Component component, Graphics g, int x, int y, int width, int height) {
+    TabbedPanel tp = TabbedUtils.getParentTabbedPanel(component);
+
+    if (tp == null)
+      return;
+
+    painter.paint(component,
+                  g,
+                  x,
+                  y,
+                  width,
+                  height,
+                  tp.getProperties().getTabAreaOrientation().getNextCW(),
+                  false,
+                  false);
+  }
+
+  public Insets getBorderInsets(Component c) {
+    return new Insets(0, 0, 0, 0);
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/border/OpenContentBorder.java b/src/net/infonode/tabbedpanel/border/OpenContentBorder.java
new file mode 100644
index 0000000..9881160
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/border/OpenContentBorder.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: OpenContentBorder.java,v 1.31 2009/02/24 13:49:23 jesper Exp $
+package net.infonode.tabbedpanel.border;
+
+import java.awt.*;
+import java.awt.geom.PathIterator;
+import java.io.Serializable;
+
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+
+import net.infonode.gui.GraphicsUtil;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.ColorProviderUtil;
+import net.infonode.gui.colorprovider.FixedColorProvider;
+import net.infonode.gui.colorprovider.UIManagerColorProvider;
+import net.infonode.tabbedpanel.Tab;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedUtils;
+import net.infonode.util.Direction;
+
+/**
+ * <p>
+ * OpenContentBorder is a border that draws a 1 pixel wide line border around a
+ * component that is used as content area component in a tabbed panel. The border
+ * also optionally draws a highlight inside the line on the top and left sides of the
+ * component. It is open, i.e. no content border will be drawn where the highlighted
+ * tab in the tabbed panel is located.
+ * </p>
+ *
+ * <p>
+ * If the highlighted tab has a {@link net.infonode.gui.shaped.border.ShapedBorder} its
+ * shape will be used to calculate where the OpenContentBorder should be open.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.31 $
+ * @see TabbedPanel
+ */
+public class OpenContentBorder implements Border, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private final ColorProvider topLeftLineColor;
+  private final ColorProvider bottomRightLineColor;
+  private final ColorProvider highlightColorProvider;
+  private int tabLeftInset = 1;
+
+  /**
+   * Constructor.
+   *
+   * @param color        the line color
+   * @param tabLeftInset the left border inset of the tab
+   */
+  public OpenContentBorder(Color color, int tabLeftInset) {
+    this(color);
+    this.tabLeftInset = tabLeftInset;
+  }
+
+  /**
+   * Constructor. Uses the TabbedPane.darkShadow color from the UIManager as line color.
+   */
+  public OpenContentBorder() {
+    this(null);
+  }
+
+  /**
+   * Constructs a OpenContentBorder without highlight and with the given color as line
+   * color.
+   *
+   * @param color the line color
+   */
+  public OpenContentBorder(Color color) {
+    this(color, null);
+  }
+
+  /**
+   * Constructs a OpenContentBorder with highlight and with the given colors as line
+   * color and highlight color.
+   *
+   * @param color          the line color
+   * @param highlightColor the highlight color
+   */
+  public OpenContentBorder(Color color, Color highlightColor) {
+    this(ColorProviderUtil.getColorProvider(color, UIManagerColorProvider.TABBED_PANE_DARK_SHADOW),
+        highlightColor == null ? null : new FixedColorProvider(highlightColor),
+                               1);
+  }
+
+  /**
+   * Constructs a OpenContentBorder with highlight and with the given colors as line
+   * color and highlight color.
+   *
+   * @param lineColor              the line color provider
+   * @param highlightColorProvider the highlight color provider
+   * @param tabLeftInset           the left border inset of the tab
+   */
+  public OpenContentBorder(ColorProvider lineColor, ColorProvider highlightColorProvider, int tabLeftInset) {
+    this(lineColor, lineColor, highlightColorProvider, tabLeftInset);
+  }
+
+  /**
+   * Constructs a OpenContentBorder with highlight and with the given colors as line
+   * color and highlight color.
+   *
+   * @param topLeftLineColor       the line color provider for the top and left lines
+   * @param bottomRightLineColor   the line color provider for the bottom and right lines
+   * @param highlightColorProvider the highlight color provider
+   * @param tabLeftInset           the left border inset of the tab
+   */
+  public OpenContentBorder(ColorProvider topLeftLineColor, ColorProvider bottomRightLineColor,
+                           ColorProvider highlightColorProvider, int tabLeftInset) {
+    this.topLeftLineColor = topLeftLineColor;
+    this.bottomRightLineColor = bottomRightLineColor;
+    this.highlightColorProvider = highlightColorProvider;
+    this.tabLeftInset = tabLeftInset;
+  }
+
+  private static int getLineIntersection(int edge, float x1, float y1, float x2, float y2, Direction orientation) {
+    return (orientation.isHorizontal() ?
+
+                                        ((x1 <= edge && x2 >= edge) || (x1 >= edge && x2 <= edge) ?
+                                                                                                   Math.round(x2 == x1 ? y2 : y1 + (edge - x1) * (y2 - y1) / (x2 - x1)) :
+                                                                                                     Integer.MAX_VALUE) :
+
+                                                                                                       ((y1 <= edge && y2 >= edge) || (y1 >= edge && y2 <= edge) ?
+                                                                                                                                                                  Math.round(y2 == y1 ? x2 : x1 + (edge - y1) * (x2 - x1) / (y2 - y1)) :
+                                                                                                                                                                    Integer.MAX_VALUE));
+  }
+
+  private static Point getTabBounds(Component c, Tab tab, Direction orientation, int x, int y, int width, int height) {
+    Rectangle r = tab.getVisibleRect();
+    r = SwingUtilities.convertRectangle(tab, r, c);
+    int start = orientation.isHorizontal() ? Math.max(y, r.y) : Math.max(x, r.x);
+    int end = start + (orientation.isHorizontal() ? r.height : r.width) - 1;
+    Shape shape = tab.getShape();
+
+    if (shape != null) {
+      int edge = orientation == Direction.UP ? tab.getHeight() :
+        orientation == Direction.RIGHT ? -1 :
+          orientation == Direction.DOWN ? -1 :
+            tab.getWidth();
+
+      float[] coords = new float[6];
+      PathIterator it = shape.getPathIterator(null);
+      it.currentSegment(coords);
+      it.next();
+      float x1 = coords[0];
+      float y1 = coords[1];
+      int min = Integer.MAX_VALUE;
+      int max = Integer.MIN_VALUE;
+
+      for (; !it.isDone(); it.next()) {
+        float lastX = coords[0];
+        float lastY = coords[1];
+        it.currentSegment(coords);
+        int li = getLineIntersection(edge, lastX, lastY, coords[0], coords[1], orientation);
+
+        if (li != Integer.MAX_VALUE) {
+          if (li < min)
+            min = li;
+
+          if (li > max)
+            max = li;
+        }
+      }
+
+      int li = getLineIntersection(edge, coords[0], coords[1], x1, y1, orientation);
+
+      if (li != Integer.MAX_VALUE) {
+        if (li < min)
+          min = li;
+
+        if (li > max)
+          max = li;
+      }
+
+      Point p0 = SwingUtilities.convertPoint(tab, 0, 0, c);
+
+      if (orientation.isHorizontal()) {
+        min += p0.y;
+        max += p0.y;
+      }
+      else {
+        min += p0.x;
+        max += p0.x;
+      }
+
+      start = Math.max(start, min);
+      end = Math.min(end, max);
+    }
+
+    return new Point(start, end);
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    TabbedPanel tabbedPanel = TabbedUtils.getParentTabbedPanelContentPanel(c).getTabbedPanel();
+
+    if (c != null && tabbedPanel != null) {
+      Tab tab = tabbedPanel.getHighlightedTab();
+      int tabStart = -1;
+      int tabEnd = -1;
+      int clipOffset = 0;
+      Direction orientation = tabbedPanel.getProperties().getTabAreaOrientation();
+
+      if (tab != null) {
+        Point p = getTabBounds(c, tab, orientation, x, y, width, height);
+        tabStart = p.x;
+        tabEnd = p.y;
+
+        Rectangle visible = tab.getVisibleRect();
+        int tabWidth = (int) visible.getWidth();
+        int tabHeight = (int) visible.getHeight();
+        clipOffset = (orientation.isHorizontal() ? tab.getHeight() > tabHeight : tab.getWidth() > tabWidth) ? -1 : 0;
+      }
+
+      Color topLeftColor = topLeftLineColor != null ? topLeftLineColor.getColor(c) : null;
+      Color bottomRightColor = bottomRightLineColor != null ? bottomRightLineColor.getColor(c) : null;
+      Color hc1 = highlightColorProvider == null ? null : highlightColorProvider.getColor(c);
+
+      if (orientation == Direction.UP && tab != null) {
+        if (topLeftColor != null) {
+          g.setColor(topLeftColor);
+          drawLine(g, x, y, tabStart - 1 + tabLeftInset, y);
+          drawLine(g, tabEnd - clipOffset, y, x + width - 1, y);
+        }
+
+        if (highlightColorProvider != null) {
+          g.setColor(hc1);
+          drawLine(g, x + 1, y + 1, tabStart + tabLeftInset - 1, y + 1);
+
+          if (tabEnd > tabStart)
+            drawLine(g, tabStart + tabLeftInset, y, tabStart + tabLeftInset, y + 1);
+
+          drawLine(g, tabEnd, y + 1, x + width - 3, y + 1);
+        }
+      }
+      else {
+        if (topLeftColor != null) {
+          g.setColor(topLeftColor);
+          drawLine(g, x, y, x + width - 1, y);
+        }
+
+        if (highlightColorProvider != null) {
+          g.setColor(hc1);
+          drawLine(g, x + 1, y + 1, x + width - (orientation == Direction.RIGHT && tabStart == 0 ? 1 : 3), y + 1);
+        }
+      }
+
+      if (orientation == Direction.LEFT && tab != null) {
+        if (topLeftColor != null) {
+          g.setColor(topLeftColor);
+          drawLine(g, x, y + 1, x, tabStart - 1 + tabLeftInset);
+          drawLine(g, x, tabEnd - clipOffset, x, y + height - 1);
+        }
+
+        if (highlightColorProvider != null) {
+          g.setColor(hc1);
+          drawLine(g, x + 1, y + 2, x + 1, tabStart + tabLeftInset - 1);
+
+          if (tabEnd > tabStart)
+            drawLine(g, x, tabStart + tabLeftInset, x + 1, tabStart + tabLeftInset);
+
+          drawLine(g, x + 1, tabEnd, x + 1, y + height - 3);
+        }
+      }
+      else {
+        if (topLeftColor != null) {
+          g.setColor(topLeftColor);
+          drawLine(g, x, y + 1, x, y + height - 1);
+        }
+
+        if (highlightColorProvider != null) {
+          g.setColor(hc1);
+          drawLine(g, x + 1, y + 2, x + 1, y + height - (orientation == Direction.DOWN && tabStart == 0 ? 1 : 3));
+        }
+      }
+
+      if (bottomRightColor != null) {
+        g.setColor(bottomRightColor);
+
+        if (orientation == Direction.RIGHT && tab != null) {
+          drawLine(g, x + width - 1, y + 1, x + width - 1, tabStart - 1 + tabLeftInset);
+          drawLine(g, x + width - 1, tabEnd - clipOffset, x + width - 1, y + height - 1);
+        }
+        else {
+          drawLine(g, x + width - 1, y + 1, x + width - 1, y + height - 1);
+        }
+
+        if (orientation == Direction.DOWN && tab != null) {
+          g.setColor(bottomRightColor);
+          drawLine(g, x + 1, y + height - 1, tabStart - 1 + tabLeftInset, y + height - 1);
+          drawLine(g, tabEnd - clipOffset, y + height - 1, x + width - 2, y + height - 1);
+        }
+        else {
+          drawLine(g, x + 1, y + height - 1, x + width - 2, y + height - 1);
+        }
+      }
+    }
+  }
+
+  private static void drawLine(Graphics graphics, int x1, int y1, int x2, int y2) {
+    if (x2 < x1 || y2 < y1)
+      return;
+
+    GraphicsUtil.drawOptimizedLine(graphics, x1, y1, x2, y2);
+    //graphics.drawLine(x1, y1, x2, y2);
+  }
+
+  public Insets getBorderInsets(Component c) {
+    int hInset = highlightColorProvider != null ? 1 : 0;
+    int tlInset = (topLeftLineColor != null ? 1 : 0) + hInset;
+    int brInset = bottomRightLineColor != null ? 1 : 0;
+    return new Insets(tlInset, tlInset, brInset, brInset);
+  }
+
+  public boolean isBorderOpaque() {
+    return true;
+  }
+
+}
diff --git a/src/net/infonode/tabbedpanel/border/TabAreaLineBorder.java b/src/net/infonode/tabbedpanel/border/TabAreaLineBorder.java
new file mode 100644
index 0000000..07f802b
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/border/TabAreaLineBorder.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabAreaLineBorder.java,v 1.19 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.border;
+
+import net.infonode.gui.GraphicsUtil;
+import net.infonode.gui.colorprovider.*;
+import net.infonode.tabbedpanel.*;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import java.awt.*;
+import java.io.Serializable;
+
+/**
+ * TabAreaLineBorder draws a 1 pixel wide border on all sides except the side towards
+ * the content area of a tabbed panel.
+ *
+ * @author $Author: jesper $
+ * @author $Author: jesper $
+ * @version $Revision: 1.19 $
+ * @see Tab
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ * @see TabAreaProperties
+ * @see TabAreaComponentsProperties
+ * @since ITP 1.1.0
+ */
+public class TabAreaLineBorder implements Border, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private ColorProvider color;
+  private boolean drawTop;
+  private boolean drawLeft;
+  private boolean drawRight;
+  private boolean flipLeftRight;
+
+  /**
+   * Constructs a TabAreaLineBorder with color based on the look and feel
+   */
+  public TabAreaLineBorder() {
+    this(null);
+  }
+
+  /**
+   * Constructs a TabAreaLineBorder with the give color
+   *
+   * @param color color for the border
+   */
+  public TabAreaLineBorder(Color color) {
+    this(color, true, true, true, false);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param drawTop       draw the top line
+   * @param drawLeft      draw the left line
+   * @param drawRight     draw the right line
+   * @param flipLeftRight if true the left line is rotated so that it is always to the left or at the top and
+   *                      vice versa for the right line, if false the left and right lines are rotated the same way as
+   *                      the other lines
+   */
+  public TabAreaLineBorder(boolean drawTop, boolean drawLeft, boolean drawRight, boolean flipLeftRight) {
+    this((Color) null, drawTop, drawLeft, drawRight, flipLeftRight);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param color         the line color
+   * @param drawTop       draw the top line
+   * @param drawLeft      draw the left line
+   * @param drawRight     draw the right line
+   * @param flipLeftRight if true the left line is rotated so that it is always to the left or at the top and
+   *                      vice versa for the right line, if false the left and right lines are rotated the same way as
+   *                      the other lines
+   */
+  public TabAreaLineBorder(Color color, boolean drawTop, boolean drawLeft, boolean drawRight, boolean flipLeftRight) {
+    this(ColorProviderUtil.getColorProvider(color, new ColorProviderList(
+        UIManagerColorProvider.TABBED_PANE_DARK_SHADOW,
+        UIManagerColorProvider.CONTROL_DARK_SHADOW,
+        FixedColorProvider.BLACK)),
+         drawTop, drawLeft, drawRight, flipLeftRight);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param colorProvider the line color provider
+   * @param drawTop       draw the top line
+   * @param drawLeft      draw the left line
+   * @param drawRight     draw the right line
+   * @param flipLeftRight if true the left line is rotated so that it is always to the left or at the top and
+   *                      vice versa for the right line, if false the left and right lines are rotated the same way as
+   *                      the other lines
+   */
+  public TabAreaLineBorder(ColorProvider colorProvider, boolean drawTop, boolean drawLeft, boolean drawRight,
+                           boolean flipLeftRight) {
+    this.color = colorProvider;
+    this.drawTop = drawTop;
+    this.drawLeft = drawLeft;
+    this.drawRight = drawRight;
+    this.flipLeftRight = flipLeftRight;
+  }
+
+  public boolean isBorderOpaque() {
+    return true;
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    Insets insets = getBorderInsets(c);
+    g.setColor(color.getColor(c));
+
+    if (insets.top == 1)
+      GraphicsUtil.drawOptimizedLine(g, x, y, x + width - 1, y);
+
+    if (insets.bottom == 1)
+      GraphicsUtil.drawOptimizedLine(g, x, y + height - 1, x + width - 1, y + height - 1);
+
+    if (insets.left == 1)
+      GraphicsUtil.drawOptimizedLine(g, x, y, x, y + height - 1);
+
+    if (insets.right == 1)
+      GraphicsUtil.drawOptimizedLine(g, x + width - 1, y, x + width - 1, y + height - 1);
+  }
+
+  private boolean drawTop(Direction orientation) {
+    return orientation == Direction.UP ? drawTop :
+           orientation == Direction.LEFT ? (flipLeftRight ? drawLeft : drawRight) :
+           orientation == Direction.RIGHT ? drawLeft :
+           false;
+  }
+
+  private boolean drawLeft(Direction orientation) {
+    return orientation == Direction.UP ? drawLeft :
+           orientation == Direction.LEFT ? drawTop :
+           orientation == Direction.DOWN ? (flipLeftRight ? drawLeft : drawRight) :
+           false;
+  }
+
+  private boolean drawRight(Direction orientation) {
+    return orientation == Direction.UP ? drawRight :
+           orientation == Direction.LEFT ? false :
+           orientation == Direction.DOWN ? (flipLeftRight ? drawRight : drawLeft) :
+           drawTop;
+  }
+
+  private boolean drawBottom(Direction orientation) {
+    return orientation == Direction.UP ? false :
+           orientation == Direction.LEFT ? (flipLeftRight ? drawRight : drawLeft) :
+           orientation == Direction.RIGHT ? drawRight :
+           drawTop;
+  }
+
+  public Insets getBorderInsets(Component c) {
+    if (c instanceof JComponent && ((JComponent) c).getComponentCount() == 0)
+      return new Insets(0, 0, 0, 0);
+
+    Direction orientation = getTabAreaDirection(c);
+
+    return orientation != null ? new Insets(drawTop(orientation) ? 1 : 0,
+                                            drawLeft(orientation) ? 1 : 0,
+                                            drawBottom(orientation) ? 1 : 0,
+                                            drawRight(orientation) ? 1 : 0) :
+           new Insets(0, 0, 0, 0);
+  }
+
+  private static Direction getTabAreaDirection(Component c) {
+    TabbedPanel tp = TabbedUtils.getParentTabbedPanel(c);
+    return tp != null ? tp.getProperties().getTabAreaOrientation() : null;
+
+  }
+
+}
diff --git a/src/net/infonode/tabbedpanel/border/TabHighlightBorder.java b/src/net/infonode/tabbedpanel/border/TabHighlightBorder.java
new file mode 100644
index 0000000..abda624
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/border/TabHighlightBorder.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabHighlightBorder.java,v 1.19 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.border;
+
+import net.infonode.gui.GraphicsUtil;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.ColorProviderUtil;
+import net.infonode.gui.colorprovider.UIManagerColorProvider;
+import net.infonode.tabbedpanel.Tab;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.TabbedUtils;
+import net.infonode.util.Direction;
+
+import javax.swing.border.Border;
+import java.awt.*;
+import java.io.Serializable;
+
+
+/**
+ * TabHighlightBorder draws a 1 pixel wide highlight on the top and left side of the
+ * tab. It will not draw highlight on the side towards a TabbedPanel's content area
+ * if the border is constructed with open border.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.19 $
+ * @see Tab
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ */
+public class TabHighlightBorder implements Border, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private ColorProvider color;
+  private boolean openBorder;
+
+  /**
+   * Constructs a TabHighlightBorder that acts as an empty border, i.e. no highlight
+   * is drawn but it will report the same insets as if the highlight was drawn
+   */
+  public TabHighlightBorder() {
+    this((Color) null, false);
+  }
+
+  /**
+   * Constructs a TabHighlightBorder with the given color as highlight color
+   *
+   * @param color      the highlight color
+   * @param openBorder when true, no highlighting is drawn on the side towards a
+   *                   TabbedPanel's content area, otherwise false
+   */
+  public TabHighlightBorder(Color color, boolean openBorder) {
+    this(ColorProviderUtil.getColorProvider(color, UIManagerColorProvider.TABBED_PANE_HIGHLIGHT), openBorder);
+  }
+
+  /**
+   * Constructs a TabHighlightBorder with the given color as highlight color
+   *
+   * @param colorProvider the highlight color provider
+   * @param openBorder    when true, no highlighting is drawn on the side towards a
+   *                      TabbedPanel's content area, otherwise false
+   */
+  public TabHighlightBorder(ColorProvider colorProvider, boolean openBorder) {
+    this.color = colorProvider;
+    this.openBorder = openBorder;
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    TabbedPanel tabbedPanel = TabbedUtils.getParentTabbedPanel(c);
+
+    if (tabbedPanel != null) {
+      Direction d = tabbedPanel.getProperties().getTabAreaOrientation();
+      g.setColor(color.getColor(c));
+
+      if (d == Direction.UP) {
+        GraphicsUtil.drawOptimizedLine(g, x + 1, y, x + width - 2, y);
+        GraphicsUtil.drawOptimizedLine(g, x, y, x, y + height - (openBorder ? 1 : 2));
+      }
+      else if (d == Direction.LEFT) {
+        GraphicsUtil.drawOptimizedLine(g, x + 1, y, x + width - (openBorder ? 1 : 2), y);
+        GraphicsUtil.drawOptimizedLine(g, x, y, x, y + height - 2);
+      }
+      else if (d == Direction.DOWN) {
+        if (!openBorder)
+          GraphicsUtil.drawOptimizedLine(g, x + 1, y, x + width - 2, y);
+        GraphicsUtil.drawOptimizedLine(g, x, y, x, y + height - 2);
+      }
+      else {
+        if (openBorder)
+          GraphicsUtil.drawOptimizedLine(g, x, y, x + width - 2, y);
+        else {
+          GraphicsUtil.drawOptimizedLine(g, x + 1, y, x + width - 2, y);
+          GraphicsUtil.drawOptimizedLine(g, x, y, x, y + height - 2);
+        }
+      }
+    }
+  }
+
+  public Insets getBorderInsets(Component c) {
+    return new Insets(1, 1, 0, 0);
+  }
+
+  public boolean isBorderOpaque() {
+    return false;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/border/TabLineBorder.java b/src/net/infonode/tabbedpanel/border/TabLineBorder.java
new file mode 100644
index 0000000..54331a4
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/border/TabLineBorder.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabLineBorder.java,v 1.27 2005/07/19 14:17:31 johan Exp $
+package net.infonode.tabbedpanel.border;
+
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.ColorProviderUtil;
+import net.infonode.gui.colorprovider.UIManagerColorProvider;
+import net.infonode.tabbedpanel.Tab;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedUtils;
+import net.infonode.util.Direction;
+
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import java.awt.*;
+import java.io.Serializable;
+
+/**
+ * TabLineBorder draws a 1 pixel wide line around a {@link Tab}. If tab spacing in the
+ * tabbed panel is 0 then the border will only draw a single line between two adjacent tabs.
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.27 $
+ * @see Tab
+ * @see TabbedPanel
+ * @deprecated As of ITP 1.2.0 use {@link TabAreaLineBorder} instead with
+ *             {@link net.infonode.tabbedpanel.TabbedPanelProperties#TAB_SPACING} set to -1.
+ */
+public class TabLineBorder implements Border, Serializable {
+  private static final long serialVersionUID = 1;
+
+  private ColorProvider color;
+  private Border border;
+  private boolean last;
+  private boolean afterHighlighted;
+  private boolean highlighted;
+  private boolean drawTopLine;
+  private boolean drawBottomLine;
+  private int index;
+  private boolean tabSpacing;
+
+  private class LineBorder implements Border {
+
+    LineBorder() {
+    }
+
+    public void paintBorder(Component component, Graphics g, int x, int y, int width, int height) {
+      Color c = color.getColor(component);
+      Tab tab = TabbedUtils.getParentTab(component);
+      if (tab != null && tab.getTabbedPanel() != null) {
+        Direction d = tab.getTabbedPanel().getProperties().getTabAreaOrientation();
+        tabSpacing = tab.getTabbedPanel().getProperties().getTabSpacing() > 0;
+        initialize(tab);
+        if (d == Direction.UP)
+          paintUpBorder(g, x, y, width, height, c);
+        else if (d == Direction.LEFT)
+          paintLeftBorder(g, x, y, width, height, c);
+        else if (d == Direction.DOWN)
+          paintDownBorder(g, x, y, width, height, c);
+        else // RIGHT
+          paintRightBorder(g, x, y, width, height, c);
+      }
+    }
+
+    public Insets getBorderInsets(Component c) {
+      Tab tab = TabbedUtils.getParentTab(c);
+      if (tab != null && tab.getTabbedPanel() != null && tab.getParent() != null) {
+        int top;
+        int left;
+        int bottom;
+        int right;
+        Direction d = tab.getTabbedPanel().getProperties().getTabAreaOrientation();
+        initialize(tab);
+        if (d == Direction.UP) {
+          top = drawTopLine ? 1 : 0;
+          left = 1;
+          bottom = 0;
+          right = 1;
+        }
+        else if (d == Direction.LEFT) {
+          top = 1;
+          left = drawTopLine ? 1 : 0;
+          bottom = 1;
+          right = 0;
+        }
+        else if (d == Direction.DOWN) {
+          top = 0;
+          left = 1;
+          bottom = drawTopLine ? 1 : 0;
+          right = 1;
+        }
+        else {
+          top = 1;
+          left = 0;
+          bottom = 1;
+          right = drawTopLine ? 1 : 0;
+        }
+
+        return new Insets(top,
+                          left,
+                          bottom,
+                          right);
+      }
+
+      return new Insets(0, 0, 0, 0);
+    }
+
+    public boolean isBorderOpaque() {
+      return true;
+    }
+
+    private void paintUpBorder(Graphics g, int x, int y, int width, int height, Color color) {
+      g.setColor(color);
+
+      // Left Line
+      if (!afterHighlighted || tabSpacing)
+        g.drawLine(x, y, x, y + height - 1);
+
+      // Right line
+      if (highlighted || last || tabSpacing)
+        g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
+
+      // Top Line
+      if (drawTopLine)
+        g.drawLine(x, y, x + width - 1, y);
+
+      if (drawBottomLine)
+        g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+    }
+
+    private void paintLeftBorder(Graphics g, int x, int y, int width, int height, Color color) {
+      g.setColor(color);
+
+      // Top Line
+      if (!afterHighlighted || tabSpacing)
+        g.drawLine(x, y, x + width - 1, y);
+
+      // Left Line
+      if (drawTopLine)
+        g.drawLine(x, y, x, y + height - 1);
+
+      // Bottom Line
+      if (highlighted || last || tabSpacing)
+        g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+
+      if (drawBottomLine)
+        g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
+    }
+
+    private void paintDownBorder(Graphics g, int x, int y, int width, int height, Color color) {
+      g.setColor(color);
+
+      // Left Line
+      if (!afterHighlighted || tabSpacing)
+        g.drawLine(x, y, x, y + height - 1);
+
+      // Right line
+      if (highlighted || last || tabSpacing)
+        g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
+
+      // Bottom Line
+      if (drawTopLine)
+        g.drawLine(x, y + height - 1, x + width, y + height - 1);
+
+      if (drawBottomLine)
+        g.drawLine(x, y, x + width, y);
+    }
+
+    private void paintRightBorder(Graphics g, int x, int y, int width, int height, Color color) {
+      g.setColor(color);
+
+      // Top Line
+      if (!afterHighlighted || tabSpacing)
+        g.drawLine(x, y, x + width - 1, y);
+
+      // Right Line
+      if (drawTopLine)
+        g.drawLine(x + width - 1, y, x + width - 1, y + height - 1);
+
+      // Bottom Line
+      if (highlighted || last || tabSpacing)
+        g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
+
+      if (drawBottomLine)
+        g.drawLine(x, y, x, y + height - 1);
+    }
+  }
+
+  /**
+   * Constructor. Uses the TabbedPane.darkShadow color from the UIManager as line color.
+   */
+  public TabLineBorder() {
+    this(null);
+  }
+
+  /**
+   * Constructs a TabLineBorder that draws lines on three sides of the tab.
+   * No line will be drawn on the side towards the TabbedPanel's content area.
+   *
+   * @param color the line color
+   */
+  public TabLineBorder(Color color) {
+    this(color, false);
+  }
+
+  /**
+   * Constructs a TabLineBorder that draws lines on three or four sides of the tab.
+   *
+   * @param color          the line color
+   * @param drawBottomLine true if a line should be drawn on the side towards the
+   *                       tabbed panel's content area, otherwise false
+   */
+  public TabLineBorder(Color color, boolean drawBottomLine) {
+    this(color, drawBottomLine, true);
+  }
+
+  /**
+   * Constructs a TabLineBorder that draws lines on two, three or four sides of the
+   * tab.
+   *
+   * @param drawBottomLine true if a line should be drawn on the side towards the
+   *                       tabbed panel's content area, otherwise false
+   * @param drawTopLine    true if a line should be drawn on the side opposite to
+   *                       the tabbed panel's content area, otherwise false
+   */
+  public TabLineBorder(boolean drawBottomLine, boolean drawTopLine) {
+    this((Color) null, drawBottomLine, drawTopLine);
+  }
+
+  /**
+   * Constructs a TabLineBorder that draws lines on two, three or four sides of the
+   * tab.
+   *
+   * @param color          the line color
+   * @param drawBottomLine true if a line should be drawn on the side towards the
+   *                       tabbed panel's content area, otherwise false
+   * @param drawTopLine    true if a line should be drawn on the side opposite to
+   *                       the tabbed panel's content area, otherwise false
+   */
+  public TabLineBorder(Color color, boolean drawBottomLine, boolean drawTopLine) {
+    this(ColorProviderUtil.getColorProvider(color, UIManagerColorProvider.TABBED_PANE_DARK_SHADOW),
+         drawBottomLine, drawTopLine);
+  }
+
+  /**
+   * Constructs a TabLineBorder that draws lines on two, three or four sides of the
+   * tab.
+   *
+   * @param colorProvider  the line color provider
+   * @param drawBottomLine true if a line should be drawn on the side towards the
+   *                       tabbed panel's content area, otherwise false
+   * @param drawTopLine    true if a line should be drawn on the side opposite to
+   *                       the tabbed panel's content area, otherwise false
+   */
+  public TabLineBorder(ColorProvider colorProvider, boolean drawBottomLine, boolean drawTopLine) {
+    this.color = colorProvider;
+    border = new LineBorder();
+    this.drawBottomLine = drawBottomLine;
+    this.drawTopLine = drawTopLine;
+  }
+
+  /**
+   * Constructs a TabLineBorder that draws lines on three sides of the tab.
+   * No line will be drawn on the side towards the tabbed panel's content area.
+   * The inner border will be drawn inside of this TabLineBorder.
+   *
+   * @param color       the line color
+   * @param innerBorder border to draw inside of this TabLineBorder
+   */
+  public TabLineBorder(Color color, Border innerBorder) {
+    this(color, innerBorder, false);
+  }
+
+  /**
+   * Constructs a TabLineBorder that draws lines on three or four sides of the tab.
+   * The inner border will be drawn inside of this TabLineBorder.
+   *
+   * @param color          the line color
+   * @param innerBorder    border to draw inside of this TabLineBorder
+   * @param drawBottomLine true if a line should be drawn on the side towards the
+   *                       tabbed panel's content area, otherwise false
+   */
+  public TabLineBorder(Color color, Border innerBorder, boolean drawBottomLine) {
+    this(color, drawBottomLine);
+    if (innerBorder != null)
+      border = new CompoundBorder(border, innerBorder);
+  }
+
+  /**
+   * Constructs a TabLineBorder that draws lines on three or four sides of the tab.
+   * The inner border will be drawn inside of this TabLineBorder.
+   *
+   * @param colorProvider  the line color
+   * @param innerBorder    border to draw inside of this TabLineBorder
+   * @param drawBottomLine true if a line should be drawn on the side towards the
+   *                       tabbed panel's content area, otherwise false
+   * @param drawTopLine    true if a line should be drawn on the side opposite to
+   *                       the tabbed panel's content area, otherwise false
+   */
+  public TabLineBorder(ColorProvider colorProvider, Border innerBorder, boolean drawBottomLine, boolean drawTopLine) {
+    this(colorProvider, drawBottomLine, drawTopLine);
+    if (innerBorder != null)
+      border = new CompoundBorder(border, innerBorder);
+  }
+
+  public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+    border.paintBorder(c, g, x, y, width, height);
+  }
+
+  public Insets getBorderInsets(Component c) {
+    return border.getBorderInsets(c);
+  }
+
+  public boolean isBorderOpaque() {
+    return false;
+  }
+
+  private void initialize(Tab tab) {
+    index = tab.getTabbedPanel().getTabIndex(tab);
+    last = index == tab.getTabbedPanel().getTabCount() - 1;
+    afterHighlighted = index > 0 &&
+                       tab.getTabbedPanel().getTabAt(index - 1) == tab.getTabbedPanel().getHighlightedTab();
+    highlighted = tab == tab.getTabbedPanel().getHighlightedTab();
+  }
+
+}
diff --git a/src/net/infonode/tabbedpanel/border/package.html b/src/net/infonode/tabbedpanel/border/package.html
new file mode 100644
index 0000000..b9aa63b
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/border/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Special borders for tabs and tabbed panels
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/hover/TabbedPanelHoverAction.java b/src/net/infonode/tabbedpanel/hover/TabbedPanelHoverAction.java
new file mode 100644
index 0000000..70af1b7
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/hover/TabbedPanelHoverAction.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelHoverAction.java,v 1.9 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.tabbedpanel.hover;
+
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+
+/**
+ * <p>
+ * TabbedPanelHoverAction is an action that makes it easy to change properties for
+ * a hovered {@link TabbedPanel}. The action is meant to be set as a {@link HoverListener}
+ * for the entire tabbed panel, the tab area, the tab area components area and/or the
+ * content area in their corresponding properties objects.
+ * </p>
+ *
+ * <p>
+ * This hover action contains a TabbedPanelProperties object that will be added as
+ * super object to the hovered tabbed panel and then automatically removed when the
+ * area is no longer hovered.
+ * </p>
+ *
+ * @author johan
+ * @version $Revision: 1.9 $
+ * @see TabbedPanel
+ * @see TabbedPanelProperties
+ * @see net.infonode.tabbedpanel.TabAreaProperties
+ * @see net.infonode.tabbedpanel.TabAreaComponentsProperties
+ * @see net.infonode.tabbedpanel.TabbedPanelContentPanelProperties
+ * @since ITP 1.3.0
+ */
+public class TabbedPanelHoverAction implements HoverListener {
+  private TabbedPanelProperties props;
+
+  /**
+   * Creates a TabbedPanelHoverAction containing an empty TabbedPanelProperties
+   * object.
+   */
+  public TabbedPanelHoverAction() {
+    this(new TabbedPanelProperties());
+  }
+
+  /**
+   * Creates a TabbedPanelHoverAction with the given TabbedPanelProperties
+   * object.
+   *
+   * @param props reference to a TabbedPanelProperties object
+   */
+  public TabbedPanelHoverAction(TabbedPanelProperties props) {
+    this.props = props;
+  }
+
+  /**
+   * Gets the TabbedPanelProperties object for this action.
+   *
+   * @return reference to the TabbedPanelProperties
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return props;
+  }
+
+  public void mouseEntered(HoverEvent event) {
+    ((TabbedPanel) event.getSource()).getProperties().addSuperObject(props);
+  }
+
+  public void mouseExited(HoverEvent event) {
+    ((TabbedPanel) event.getSource()).getProperties().removeSuperObject(props);
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/hover/TabbedPanelTitledTabHoverAction.java b/src/net/infonode/tabbedpanel/hover/TabbedPanelTitledTabHoverAction.java
new file mode 100644
index 0000000..2407a14
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/hover/TabbedPanelTitledTabHoverAction.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelTitledTabHoverAction.java,v 1.12 2005/04/20 15:43:28 johan Exp $
+
+package net.infonode.tabbedpanel.hover;
+
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.tabbedpanel.*;
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+/**
+ * <p>
+ * TabbedPanelTitledTabHoverAction is an action that makes it easy to change
+ * properties for a hovered {@link TabbedPanel} containing {@link TitledTab}s.
+ * The action is meant to be set as a {@link HoverListener} for the entire
+ * tabbed panel, the tab area, the tab area components area and/or the content
+ * area in their corresponding properties objects.
+ * </p>
+ *
+ * <p>
+ * The action can be configured to add the TitledTabProperties to all tabs or
+ * only the highlighted tab.
+ * </p>
+ *
+ * <p>
+ * This hover action contains a TabbedPanelProperties object that will be added
+ * as super object to the hovered tabbed panel and then automatically removed
+ * when the area is no longer hovered. It also contains a TitledTabProperties
+ * object that will be added as super object to all titled tabs in the hovered
+ * tabbed panel and then removed when the tabbed panel is no longer hovered.
+ * </p>
+ *
+ * <p>
+ * If a titled tab is added to the tabbed panel while the tabbed panel is
+ * hovered, the action will automatically add the TitledTabProperties to the
+ * titled tab. If a titled tab is removed while the tabbed panel is hovered, the
+ * properties will automatically be removed.
+ * </p>
+ *
+ * <p>
+ * <strong>Note: </strong> This action is not meant to be set as hover listener
+ * in the TitledTabProperties for a titled tab. For TitledTab, use
+ * {@link net.infonode.tabbedpanel.hover.TitledTabTabbedPanelHoverAction} instead.
+ * </p>
+ *
+ * @author johan
+ * @version $Revision: 1.12 $
+ * @see TabbedPanel
+ * @see TitledTab
+ * @see TabbedPanelProperties
+ * @see net.infonode.tabbedpanel.TabAreaProperties
+ * @see net.infonode.tabbedpanel.TabAreaComponentsProperties
+ * @see net.infonode.tabbedpanel.TabbedPanelContentPanelProperties
+ * @see net.infonode.tabbedpanel.hover.TitledTabTabbedPanelHoverAction
+ * @see TitledTabProperties
+ * @since ITP 1.3.0
+ */
+public class TabbedPanelTitledTabHoverAction implements HoverListener {
+  private TabbedPanelProperties tabbedPanelProperties;
+  private TitledTabProperties titledTabProperties;
+  private boolean onlyHighlighted;
+
+  private TabAdapter tabListener = new TabAdapter() {
+    public void tabAdded(TabEvent event) {
+      if (event.getTab() instanceof TitledTab) {
+        if (!onlyHighlighted || (onlyHighlighted && event.getTab().isHighlighted())) {
+          ((TitledTab) event.getTab()).getProperties().addSuperObject(titledTabProperties);
+        }
+      }
+    }
+
+    public void tabRemoved(TabRemovedEvent event) {
+      if (event.getTab() instanceof TitledTab)
+        ((TitledTab) event.getTab()).getProperties().removeSuperObject(titledTabProperties);
+    }
+
+    public void tabHighlighted(TabStateChangedEvent event) {
+      if (event.getCurrentTab() != null && event.getCurrentTab() instanceof TitledTab)
+        applyTitledTabProperties(event.getTabbedPanel(), ((TitledTab) event.getCurrentTab()));
+    }
+
+    public void tabDehighlighted(TabStateChangedEvent event) {
+      if (event.getPreviousTab() != null && event.getPreviousTab() instanceof TitledTab)
+        removeTitledTabProperties(event.getTabbedPanel(), ((TitledTab) event.getPreviousTab()));
+    }
+  };
+
+  /**
+   * Creates a TabbedPanelTitledTabHoverAction containing an empty
+   * TabbedPanelProperties object and an empty TitledTabProperties object. The
+   * TitledTabProperties are only applied to the highlighted tab.
+   */
+  public TabbedPanelTitledTabHoverAction() {
+    this(false);
+  }
+
+  /**
+   * Creates a TabbedPanelTitledTabHoverAction containing an empty
+   * TabbedPanelProperties object and an empty TitledTabProperties object.
+   *
+   * @param allTabs true if TitledTabProperties should be applied to all tabs,
+   *                false if only to the highlighted tab
+   */
+  public TabbedPanelTitledTabHoverAction(boolean allTabs) {
+    this(new TabbedPanelProperties(), new TitledTabProperties(), allTabs);
+  }
+
+  /**
+   * Creates a TabbedPanelTitledTabHoverAction with the given
+   * TabbedPanelProperties object and the given TitledTabProperties object.
+   * The TitledTabProperties are only applied to the highlighted tab.
+   *
+   * @param tabbedPanelProperties reference to a TabbedPanelProperties object
+   * @param titledTabProperties   reference to a TitledTabProperties object
+   */
+  public TabbedPanelTitledTabHoverAction(TabbedPanelProperties tabbedPanelProperties,
+                                         TitledTabProperties titledTabProperties) {
+    this(tabbedPanelProperties, titledTabProperties, false);
+  }
+
+  /**
+   * Creates a TabbedPanelTitledTabHoverAction with the given
+   * TabbedPanelProperties object and the given TitledTabProperties object.
+   *
+   * @param tabbedPanelProperties reference to a TabbedPanelProperties object
+   * @param titledTabProperties   reference to a TitledTabProperties object
+   * @param allTabs               true if TitledTabProperties should be applied to all tabs,
+   *                              false if only to the highlighted tab
+   */
+  public TabbedPanelTitledTabHoverAction(TabbedPanelProperties tabbedPanelProperties,
+                                         TitledTabProperties titledTabProperties,
+                                         boolean allTabs) {
+    this.tabbedPanelProperties = tabbedPanelProperties;
+    this.titledTabProperties = titledTabProperties;
+    this.onlyHighlighted = !allTabs;
+  }
+
+  /**
+   * Gets the TitledTabProperties object for this action.
+   *
+   * @return reference to the TitledTabProperties
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return titledTabProperties;
+  }
+
+  /**
+   * Gets the TabbedPanelProperties object for this action.
+   *
+   * @return reference to the TabbedPanelProperties
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return tabbedPanelProperties;
+  }
+
+  public void mouseEntered(HoverEvent event) {
+    TabbedPanel tp = (TabbedPanel) event.getSource();
+    tp.getProperties().addSuperObject(tabbedPanelProperties);
+    tp.addTabListener(tabListener);
+    if (tp.getHighlightedTab() != null && tp.getHighlightedTab() instanceof TitledTab)
+      applyTitledTabProperties(tp, ((TitledTab) tp.getHighlightedTab()));
+    else
+      applyTitledTabProperties(tp, null);
+  }
+
+  public void mouseExited(HoverEvent event) {
+    TabbedPanel tp = (TabbedPanel) event.getSource();
+    tp.getProperties().removeSuperObject(tabbedPanelProperties);
+    tp.removeTabListener(tabListener);
+    if (tp.getHighlightedTab() != null && tp.getHighlightedTab() instanceof TitledTab)
+      removeTitledTabProperties(tp, ((TitledTab) tp.getHighlightedTab()));
+    else
+      removeTitledTabProperties(tp, null);
+  }
+
+  private void applyTitledTabProperties(TabbedPanel tabbedPanel, TitledTab titledTab) {
+    if (onlyHighlighted) {
+      if (titledTab != null)
+        titledTab.getProperties().addSuperObject(titledTabProperties);
+    }
+    else {
+      for (int i = 0; i < tabbedPanel.getTabCount(); i++) {
+        Tab tab = tabbedPanel.getTabAt(i);
+        if (tab instanceof TitledTab)
+          ((TitledTab) tab).getProperties().addSuperObject(titledTabProperties);
+      }
+    }
+  }
+
+  private void removeTitledTabProperties(TabbedPanel tabbedPanel, TitledTab titledTab) {
+    if (onlyHighlighted) {
+      if (titledTab != null)
+        titledTab.getProperties().removeSuperObject(titledTabProperties);
+    }
+    else {
+      for (int i = 0; i < tabbedPanel.getTabCount(); i++) {
+        Tab tab = tabbedPanel.getTabAt(i);
+        if (tab instanceof TitledTab)
+          ((TitledTab) tab).getProperties().removeSuperObject(titledTabProperties);
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/hover/TitledTabDelayedMouseExitHoverAction.java b/src/net/infonode/tabbedpanel/hover/TitledTabDelayedMouseExitHoverAction.java
new file mode 100644
index 0000000..9642f9b
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/hover/TitledTabDelayedMouseExitHoverAction.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitledTabDelayedMouseExitHoverAction.java,v 1.6 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.hover;
+
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.gui.hover.action.DelayedHoverExitAction;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+import javax.swing.*;
+
+/**
+ * <p>
+ * TitledTabDelayedMouseExitHoverAction is an action that wraps a {@link HoverListener} and delays
+ * the mouse exit when a {@link TitledTab} is no longer hovered. The action is meant to be set
+ * as a {@link HoverListener} in the {@link TitledTabProperties}.
+ * </p>
+ *
+ * <p>
+ * If the TitledTab is hovered again before the delay has timed out, the timer is reset. If the
+ * TitledTab is removed before the delay has timed out the hover listener's mouseExit() will be
+ * called immediately.
+ * </p>
+ *
+ * @author johan
+ * @version $Revision: 1.6 $
+ * @see TitledTab
+ * @see TitledTabProperties
+ * @since ITP 1.3.0
+ */
+public class TitledTabDelayedMouseExitHoverAction implements HoverListener {
+  private DelayedHoverExitAction delayedAction;
+  private HoverListener hoverListener;
+
+  /**
+   * Creates a TitledTabDelayedMouseExitHoverAction object with the given HoverListener as action
+   *
+   * @param delay         delay in milliseconds before the hover listener is called when the
+   *                      titled tab is no longer hovered
+   * @param hoverListener reference to a HoverListener
+   */
+  public TitledTabDelayedMouseExitHoverAction(int delay, HoverListener hoverListener) {
+    this.hoverListener = hoverListener;
+
+    delayedAction = new DelayedHoverExitAction(new HoverListener() {
+      public void mouseEntered(HoverEvent event) {
+        getHoverListener().mouseEntered(event);
+      }
+
+      public void mouseExited(HoverEvent event) {
+        getHoverListener().mouseExited(event);
+      }
+    }, delay);
+  }
+
+  /**
+   * Gets the hover listener
+   *
+   * @return the hoverListener.
+   */
+  public HoverListener getHoverListener() {
+    return hoverListener;
+  }
+
+  /**
+   * Gets the TitledTabProperties object for this action.
+   *
+   * @return reference to the TitledTabProperties or null
+   *         if the delayed action is not a TitledTabHoverAction
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    if (getHoverListener() instanceof TitledTabHoverAction)
+      return ((TitledTabHoverAction) getHoverListener()).getTitledTabProperties();
+
+    return null;
+  }
+
+  public void mouseEntered(HoverEvent event) {
+    delayedAction.mouseEntered(event);
+  }
+
+  public void mouseExited(HoverEvent event) {
+    final TitledTab tab = (TitledTab) event.getSource();
+    final TabbedPanel tp = tab.getTabbedPanel();
+
+    delayedAction.mouseExited(event);
+
+    SwingUtilities.invokeLater(new Runnable() {
+      public void run() {
+        if (tab.getTabbedPanel() != tp) {
+          delayedAction.forceExit(tab);
+        }
+      }
+    });
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/hover/TitledTabHoverAction.java b/src/net/infonode/tabbedpanel/hover/TitledTabHoverAction.java
new file mode 100644
index 0000000..a85bcc5
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/hover/TitledTabHoverAction.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitledTabHoverAction.java,v 1.12 2005/02/16 11:28:14 jesper Exp $
+
+package net.infonode.tabbedpanel.hover;
+
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+/**
+ * <p>
+ * TitledTabHoverAction is an action that makes it easy to change properties for
+ * a hovered {@link TitledTab}. The action is meant to be set as a {@link HoverListener}
+ * in the {@link TitledTabProperties}.
+ * </p>
+ *
+ * <p>
+ * This hover action contains a TitledTabProperties object that will be added as
+ * super object to the hovered titled tab and then automatically removed when the
+ * titled tab is no longer hovered.
+ * </p>
+ *
+ * @author johan
+ * @version $Revision: 1.12 $
+ * @see TitledTab
+ * @see TitledTabProperties
+ * @since ITP 1.3.0
+ */
+public class TitledTabHoverAction implements HoverListener {
+  private TitledTabProperties props;
+
+  /**
+   * Creates a TitledTabHoverAction containing an empty TitledTabProperties
+   * object.
+   */
+  public TitledTabHoverAction() {
+    this(new TitledTabProperties());
+  }
+
+  /**
+   * Creates a TitledTabHoverAction with the given TitledTabProperties
+   * object.
+   *
+   * @param props reference to a TitledTabProperties object
+   */
+  public TitledTabHoverAction(TitledTabProperties props) {
+    this.props = props;
+  }
+
+  /**
+   * Gets the TitledTabProperties object for this action.
+   *
+   * @return reference to the TitledTabProperties
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return props;
+  }
+
+  public void mouseEntered(HoverEvent event) {
+    ((TitledTab) event.getSource()).getProperties().addSuperObject(props);
+  }
+
+  public void mouseExited(HoverEvent event) {
+    ((TitledTab) event.getSource()).getProperties().removeSuperObject(props);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/hover/TitledTabTabbedPanelHoverAction.java b/src/net/infonode/tabbedpanel/hover/TitledTabTabbedPanelHoverAction.java
new file mode 100644
index 0000000..16e056f
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/hover/TitledTabTabbedPanelHoverAction.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitledTabTabbedPanelHoverAction.java,v 1.5 2005/02/16 11:28:14 jesper Exp $
+
+package net.infonode.tabbedpanel.hover;
+
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.tabbedpanel.TabAdapter;
+import net.infonode.tabbedpanel.TabStateChangedEvent;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+/**
+ * <p>
+ * TitledTabTabbedPanelHoverAction is an action that makes it easy to change
+ * properties for a hovered {@link TitledTab} and the {@link TabbedPanel} it is a
+ * member of. The action is meant to be set as a {@link HoverListener} for a
+ * TitledTab in the
+ * {@link net.infonode.tabbedpanel.titledtab.TitledTabProperties}.
+ * </p>
+ *
+ * <p>
+ * The action can be configured to add the TabbedPanelProperties only when the
+ * highlighted TitledTab is hovered or when any of the TitledTabs are hovered.
+ * </p>
+ *
+ * <p>
+ * This hover action contains a TitledTabProperties object that will be added as
+ * super object to the hovered titled tab and then automatically removed when
+ * the titled tab is no longer hovered. It also contains a TabbedPanelProperties
+ * object that will be added as super object to the tabbed panel that the
+ * hovered titled tab is a member of. The TabbedPanelProperties are
+ * automatically removed from the tabbed panel if the hovered titled tab is
+ * removed.
+ * </p>
+ *
+ * <p>
+ * <strong>Note: </strong> This action is not meant to be set as hover listener
+ * for a Tabbed Panel (or any of its areas). For TabbedPanel, use
+ * {@link net.infonode.tabbedpanel.hover.TabbedPanelTitledTabHoverAction} instead.
+ * </p>
+ *
+ * @author johan
+ * @version $Revision: 1.5 $
+ * @see TabbedPanel
+ * @see TitledTab
+ * @see net.infonode.tabbedpanel.titledtab.TitledTabProperties
+ * @see TabbedPanelProperties
+ * @see net.infonode.tabbedpanel.hover.TabbedPanelTitledTabHoverAction
+ * @since ITP 1.3.0
+ */
+public class TitledTabTabbedPanelHoverAction implements HoverListener {
+  private TabbedPanelProperties tabbedPanelProperties;
+  private TitledTabProperties titledTabProperties;
+
+  private boolean applied = false;
+  private boolean onlyHighlighted;
+
+  private TabAdapter tabListener = new TabAdapter() {
+    public void tabHighlighted(TabStateChangedEvent event) {
+      if (event.getCurrentTab() != null && onlyHighlighted)
+        applyTabbedPanel(event.getCurrentTab().getTabbedPanel());
+    }
+
+    public void tabDehighlighted(TabStateChangedEvent event) {
+      if (event.getPreviousTab() != null && onlyHighlighted)
+        removeTabbedPanel(event.getPreviousTab().getTabbedPanel());
+    }
+  };
+
+  /**
+   * Creates a TitledTabTabbedPanelHoverAction containing an empty
+   * TitledTabProperties object and an empty TabbedPanelProperties object. The
+   * TabbedPanelProperties are only applied when the highlighted tab is
+   * hovered.
+   */
+  public TitledTabTabbedPanelHoverAction() {
+    this(new TitledTabProperties(), new TabbedPanelProperties());
+  }
+
+  /**
+   * Creates a TitledTabTabbedPanelHoverAction containing an empty
+   * TitledTabProperties object and an empty TabbedPanelProperties object.
+   *
+   * @param allTabs true if the TabbedPanelProperties should be applied to the
+   *                tabbed panel when a tab is hovered, false if it should only be
+   *                applied when the the highlighted tab is hovered
+   */
+  public TitledTabTabbedPanelHoverAction(boolean allTabs) {
+    this(new TitledTabProperties(), new TabbedPanelProperties(), allTabs);
+  }
+
+  /**
+   * Creates a TitledTabTabbedPanelHoverAction containing with the given
+   * TitledTabProperties object and the given TabbedPanelProperties object.
+   * The TabbedPanelProperties are only applied when the highlighted tab is
+   * hovered.
+   *
+   * @param titledTabProperties   reference to a TitledTabProperties object
+   * @param tabbedPanelProperties reference to a TabbedPanelProperties object
+   */
+  public TitledTabTabbedPanelHoverAction(TitledTabProperties titledTabProperties,
+                                         TabbedPanelProperties tabbedPanelProperties) {
+    this(titledTabProperties, tabbedPanelProperties, false);
+  }
+
+  /**
+   * Creates a TitledTabTabbedPanelHoverAction containing with the given
+   * TitledTabProperties object and the given TabbedPanelProperties object.
+   *
+   * @param titledTabProperties   reference to a TitledTabProperties object
+   * @param tabbedPanelProperties reference to a TabbedPanelProperties object
+   * @param allTabs               true if the TabbedPanelProperties should be applied to the
+   *                              tabbed panel when a tab is hovered, false if it should only be
+   *                              applied when the the highlighted tab is hovered
+   */
+  public TitledTabTabbedPanelHoverAction(TitledTabProperties titledTabProperties,
+                                         TabbedPanelProperties tabbedPanelProperties,
+                                         boolean allTabs) {
+    this.titledTabProperties = titledTabProperties;
+    this.tabbedPanelProperties = tabbedPanelProperties;
+    this.onlyHighlighted = !allTabs;
+  }
+
+  /**
+   * Gets the TitledTabProperties object for this action.
+   *
+   * @return reference to the TitledTabProperties
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return titledTabProperties;
+  }
+
+  /**
+   * Gets the TabbedPanelProperties object for this action.
+   *
+   * @return reference to the TabbedPanelProperties
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return tabbedPanelProperties;
+  }
+
+  public void mouseEntered(HoverEvent event) {
+    TitledTab tab = (TitledTab) event.getSource();
+    if (onlyHighlighted) {
+      tab.addTabListener(tabListener);
+      if (tab.isHighlighted())
+        applyTabbedPanel(tab.getTabbedPanel());
+    }
+    else {
+      applyTabbedPanel(tab.getTabbedPanel());
+    }
+
+    tab.getProperties().addSuperObject(titledTabProperties);
+  }
+
+  public void mouseExited(HoverEvent event) {
+    TitledTab tab = (TitledTab) event.getSource();
+    removeTabbedPanel(tab.getTabbedPanel());
+    if (onlyHighlighted)
+      tab.removeTabListener(tabListener);
+    tab.getProperties().removeSuperObject(titledTabProperties);
+  }
+
+  private void applyTabbedPanel(TabbedPanel tabbedPanel) {
+    if (!applied) {
+      tabbedPanel.getProperties().addSuperObject(tabbedPanelProperties);
+      applied = true;
+    }
+  }
+
+  private void removeTabbedPanel(TabbedPanel tabbedPanel) {
+    if (applied) {
+      tabbedPanel.getProperties().removeSuperObject(tabbedPanelProperties);
+      applied = false;
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/hover/package.html b/src/net/infonode/tabbedpanel/hover/package.html
new file mode 100644
index 0000000..da6f93d
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/hover/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Contains hover actions that make it easy to change properties for hovered tabbed panels and titled tabs.
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/info/Info.java b/src/net/infonode/tabbedpanel/info/Info.java
new file mode 100644
index 0000000..49bd255
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/info/Info.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Info.java,v 1.8 2005/12/04 13:46:05 jesper Exp $
+
+package net.infonode.tabbedpanel.info;
+
+import net.infonode.gui.ReleaseInfoDialog;
+import net.infonode.gui.laf.InfoNodeLookAndFeelReleaseInfo;
+import net.infonode.tabbedpanel.TabbedPanelReleaseInfo;
+import net.infonode.util.ReleaseInfo;
+
+/**
+ * Program that shows release information in a dialog
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ */
+public class Info {
+  private Info() {
+  }
+
+  public static final void main(String[] args) {
+    ReleaseInfoDialog.showDialog(
+        new ReleaseInfo[]{TabbedPanelReleaseInfo.getReleaseInfo(), InfoNodeLookAndFeelReleaseInfo.getReleaseInfo()},
+        null);
+    System.exit(0);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/internal/ShadowPainter.java b/src/net/infonode/tabbedpanel/internal/ShadowPainter.java
new file mode 100644
index 0000000..e6404c1
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/internal/ShadowPainter.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ShadowPainter.java,v 1.9 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.internal;
+
+import net.infonode.gui.ComponentUtil;
+import net.infonode.gui.GraphicsUtil;
+import net.infonode.gui.UIManagerUtil;
+import net.infonode.util.ColorUtil;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ * @since ITP 1.1.0
+ */
+public class ShadowPainter {
+  private Color panelBackgroundColor;
+  private Color tabBackgroundColor;
+  private Component component;
+  private JComponent componentsPanel;
+  private JComponent highlightedTab;
+  private JComponent contentPanel;
+  private JComponent tabAreaComponentsPanel;
+  private JComponent tabAreaContainer;
+  private JComponent tabBox;
+  private Direction tabOrientation;
+  private boolean paintTabAreaShadow;
+  private int shadowSize;
+  private int shadowBlendSize;
+  private Color shadowColor;
+  private float shadowStrength;
+  private boolean highlightedTabIsLast;
+
+  public ShadowPainter(Component component, JComponent componentsPanel, JComponent highlightedTab,
+                       JComponent contentPanel, JComponent tabAreaComponentsPanel, JComponent tabAreaContainer,
+                       JComponent tabBox, Direction tabOrientation, boolean paintTabAreaShadow,
+                       int shadowSize, int shadowBlendSize, Color shadowColor,
+                       float shadowStrength, boolean highlightedTabIsLast) {
+    this.component = component;
+    this.componentsPanel = componentsPanel;
+    this.highlightedTab = highlightedTab == null ?
+                          null : !highlightedTab.isVisible() || !tabAreaContainer.isVisible() ? null : highlightedTab;
+    this.contentPanel = contentPanel;
+    this.tabAreaComponentsPanel = tabAreaComponentsPanel;
+    this.tabAreaContainer = tabAreaContainer;
+    this.tabBox = tabBox;
+    this.tabOrientation = tabOrientation;
+    this.paintTabAreaShadow = paintTabAreaShadow && tabAreaContainer.isVisible();
+    this.shadowSize = shadowSize;
+    this.shadowBlendSize = shadowBlendSize;
+    this.shadowColor = shadowColor;
+    this.shadowStrength = shadowStrength;
+    this.highlightedTabIsLast = highlightedTabIsLast;
+  }
+
+  public void paint(Graphics g) {
+    panelBackgroundColor = ComponentUtil.getBackgroundColor(component);
+    panelBackgroundColor = panelBackgroundColor == null ?
+                           UIManagerUtil.getColor("Panel.background", "control") : panelBackgroundColor;
+
+    if (paintTabAreaShadow) {
+      Rectangle bounds = SwingUtilities.calculateInnerArea(componentsPanel, new Rectangle());
+
+      // Right shadow
+      drawBottomRightEdgeShadow(g, bounds.y, bounds.x + bounds.width, bounds.height, true, panelBackgroundColor);
+
+      // Bottom shadow
+      drawBottomRightEdgeShadow(g, bounds.x, bounds.y + bounds.height, bounds.width, false, panelBackgroundColor);
+
+      // Bottom right corner
+      drawRightCornerShadow(g, bounds.x + bounds.width, bounds.y + bounds.height, false, panelBackgroundColor);
+    }
+    else {
+      tabBackgroundColor = highlightedTab == null ?
+                           panelBackgroundColor :
+                           ComponentUtil.getBackgroundColor(highlightedTab.getParent());
+
+      tabBackgroundColor = tabBackgroundColor == null ? panelBackgroundColor : tabBackgroundColor;
+
+      Rectangle contentPanelBounds = contentPanel.getBounds();
+      int len = 0;
+
+      if (highlightedTab != null)
+        len = paintHighlightedTabShadow(g, tabOrientation, contentPanelBounds);
+
+      if (tabAreaComponentsPanel.isVisible()) {
+        len = tabOrientation.isHorizontal() ?
+              (tabAreaContainer.getInsets().bottom == 0 ? tabAreaComponentsPanel.getWidth() : 0) :
+              (tabAreaContainer.getInsets().right == 0 ? tabAreaComponentsPanel.getHeight() : 0);
+      }
+
+      if (!tabAreaContainer.isVisible())
+        len = 0;
+
+      if (tabOrientation != Direction.RIGHT || highlightedTab == null)
+        drawBottomRightEdgeShadow(g,
+                                  contentPanelBounds.y - (tabOrientation == Direction.UP ? len : 0),
+                                  contentPanelBounds.x + contentPanelBounds.width,
+                                  contentPanelBounds.height + (!tabOrientation.isHorizontal() ? len : 0),
+                                  true,
+                                  highlightedTab == null ? null : panelBackgroundColor);
+
+      if (tabOrientation != Direction.DOWN || highlightedTab == null)
+        drawBottomRightEdgeShadow(g,
+                                  contentPanelBounds.x - (tabOrientation == Direction.LEFT ? len : 0),
+                                  contentPanelBounds.y + contentPanelBounds.height,
+                                  contentPanelBounds.width + (tabOrientation.isHorizontal() ? len : 0),
+                                  false,
+                                  highlightedTab == null ? null : panelBackgroundColor);
+
+      drawRightCornerShadow(g,
+                            contentPanelBounds.x + contentPanelBounds.width + (tabOrientation == Direction.RIGHT ?
+                                                                               len : 0),
+                            contentPanelBounds.y + contentPanelBounds.height + (tabOrientation == Direction.DOWN ?
+                                                                                len : 0),
+                            false,
+                            panelBackgroundColor);
+    }
+
+  }
+
+  private int paintHighlightedTabShadow(Graphics g, Direction tabOrientation, Rectangle contentPanelBounds) {
+    Point p = SwingUtilities.convertPoint(highlightedTab.getParent(), highlightedTab.getLocation(), component);
+
+//    JComponent tabBox = draggableComponentBox;
+    Dimension tabsSize = tabBox.getSize();
+    Rectangle bounds = tabAreaComponentsPanel.isVisible() ?
+                       SwingUtilities.convertRectangle(tabAreaComponentsPanel.getParent(),
+                                                       tabAreaComponentsPanel.getBounds(),
+                                                       component) :
+                       new Rectangle(contentPanelBounds.x + contentPanelBounds.width,
+                                     contentPanelBounds.y + contentPanelBounds.height,
+                                     0,
+                                     0);
+    Point tabsPos = SwingUtilities.convertPoint(tabBox, 0, 0, component);
+
+    // Set tab clip
+    int width = (tabOrientation.isHorizontal() ? 0 : tabsPos.x) + tabsSize.width;
+    int height = (tabOrientation.isHorizontal() ? tabsPos.y : 0) + tabsSize.height;
+
+    if (tabOrientation == Direction.DOWN)
+      drawBottomRightTabShadow(g,
+                               contentPanelBounds.x,
+                               contentPanelBounds.y + contentPanelBounds.height,
+                               contentPanelBounds.width,
+                               p.x,
+                               highlightedTab.getWidth(),
+                               highlightedTab.getHeight(),
+                               bounds.x,
+                               bounds.width,
+                               bounds.height,
+                               false,
+                               highlightedTabIsLast);
+    else if (tabOrientation == Direction.RIGHT)
+      drawBottomRightTabShadow(g,
+                               contentPanelBounds.y,
+                               contentPanelBounds.x + contentPanelBounds.width,
+                               contentPanelBounds.height,
+                               p.y,
+                               highlightedTab.getHeight(),
+                               highlightedTab.getWidth(),
+                               bounds.y,
+                               bounds.height,
+                               bounds.width,
+                               true,
+                               highlightedTabIsLast);
+    else if (tabOrientation == Direction.UP) {
+      drawTopLeftTabShadow(g,
+                           p.x + highlightedTab.getWidth(),
+                           p.y, highlightedTab.getHeight(),
+                           bounds,
+                           contentPanelBounds.width,
+                           false,
+                           highlightedTabIsLast);
+    }
+    else
+      drawTopLeftTabShadow(g,
+                           p.y + highlightedTab.getHeight(),
+                           p.x,
+                           highlightedTab.getWidth(),
+                           flipRectangle(bounds),
+                           contentPanelBounds.height,
+                           true,
+                           highlightedTabIsLast);
+
+    if (highlightedTabIsLast) {
+      return tabOrientation.isHorizontal() ? (p.y + highlightedTab.getHeight() >= contentPanelBounds.height &&
+                                              contentPanelBounds.height == height ? highlightedTab.getWidth() : 0) :
+             (p.x + highlightedTab.getWidth() >= contentPanelBounds.width &&
+              contentPanelBounds.width == width ? highlightedTab.getHeight() : 0);
+    }
+    else
+      return 0;
+  }
+
+  private static Rectangle flipRectangle(Rectangle bounds) {
+    return new Rectangle(bounds.y, bounds.x, bounds.height, bounds.width);
+  }
+
+  private static Rectangle createRectangle(int x, int y, int width, int height, boolean flip) {
+    return flip ? new Rectangle(y, x, height, width) : new Rectangle(x, y, width, height);
+  }
+
+  private void drawTopLeftTabShadow(Graphics g, int x, int y, int height, Rectangle componentsBounds,
+                                    int totalWidth, boolean flip, boolean isLast) {
+    boolean connected = x + shadowSize > componentsBounds.x;
+
+    if (!connected || y + shadowSize + shadowBlendSize <= componentsBounds.y) {
+      drawLeftCornerShadow(g, y, Math.min(x, componentsBounds.x), !flip, isLast ? tabBackgroundColor : null);
+      drawEdgeShadow(g,
+                     y,
+                     connected ? componentsBounds.y : y + height,
+                     Math.min(x, componentsBounds.x),
+                     false,
+                     !flip,
+                     isLast ? tabBackgroundColor : null);
+    }
+
+    int endX = componentsBounds.x + componentsBounds.width;
+
+    if (endX < totalWidth) {
+      drawLeftCornerShadow(g, componentsBounds.y, endX, !flip, tabBackgroundColor);
+      drawEdgeShadow(g,
+                     componentsBounds.y,
+                     componentsBounds.y + componentsBounds.height,
+                     endX,
+                     false,
+                     !flip,
+                     tabBackgroundColor);
+    }
+  }
+
+  private void drawBottomRightTabShadow(Graphics g,
+                                        int x,
+                                        int y,
+                                        int width,
+                                        int tabX,
+                                        int tabWidth,
+                                        int tabHeight,
+                                        int componentsX,
+                                        int componentsWidth,
+                                        int componentsHeight,
+                                        boolean flip,
+                                        boolean isLast) {
+    Shape oldClipRect = g.getClip();
+
+    {
+      Rectangle clipRect = createRectangle(x, y, Math.min(tabX, componentsX) - x, 1000000, flip);
+      g.clipRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
+    }
+
+    drawLeftCornerShadow(g, x, y, flip, null);
+    drawEdgeShadow(g, x, tabX, y, false, flip, null);
+
+    boolean connected = tabX < componentsX && tabX + tabWidth >= componentsX;
+    g.setClip(oldClipRect);
+
+    if (!connected) {
+      Rectangle clipRect = createRectangle(x, y, componentsX - x, 1000000, flip);
+      g.clipRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
+    }
+
+    if (tabX < componentsX) {
+      int endX = connected && tabHeight == componentsHeight ? componentsX + componentsWidth :
+                 Math.min(componentsX, tabX + tabWidth);
+
+      if (tabX + shadowSize < endX)
+        drawLeftCornerShadow(g, tabX, y + tabHeight, flip, panelBackgroundColor);
+
+      drawEdgeShadow(g, tabX, endX, y + tabHeight, false, flip, panelBackgroundColor);
+
+      if (endX < x + width && (!connected || componentsHeight < tabHeight)) {
+        drawRightCornerShadow(g, endX, y + tabHeight, flip, panelBackgroundColor);
+        drawEdgeShadow(g,
+                       y + (connected ? componentsHeight : 0),
+                       y + tabHeight,
+                       endX,
+                       true,
+                       !flip,
+                       isLast ? tabBackgroundColor : null);
+      }
+    }
+
+    if (!connected) {
+      drawEdgeShadow(g, tabX + tabWidth, componentsX, y, true, flip, isLast ? tabBackgroundColor : null);
+      g.setClip(oldClipRect);
+    }
+
+    if (componentsHeight > 0 && (!connected || tabHeight != componentsHeight)) {
+      if (!connected || componentsHeight > tabHeight) {
+        drawLeftCornerShadow(g, componentsX, y + componentsHeight, flip, panelBackgroundColor);
+        drawEdgeShadow(g,
+                       componentsX,
+                       componentsX + componentsWidth,
+                       y + componentsHeight,
+                       false,
+                       flip,
+                       panelBackgroundColor);
+      }
+      else
+        drawEdgeShadow(g,
+                       componentsX,
+                       componentsX + componentsWidth,
+                       y + componentsHeight,
+                       true,
+                       flip,
+                       panelBackgroundColor);
+    }
+
+    if (componentsX + componentsWidth < x + width) {
+      drawRightCornerShadow(g, componentsX + componentsWidth, y + componentsHeight, flip, panelBackgroundColor);
+      drawEdgeShadow(g, y, y + componentsHeight, componentsX + componentsWidth, true, !flip, panelBackgroundColor);
+      drawEdgeShadow(g, componentsX + componentsWidth, x + width, y, true, flip, panelBackgroundColor);
+    }
+  }
+
+  private void drawBottomRightEdgeShadow(Graphics g, int x, int y, int width, boolean flip, Color backgroundColor) {
+    drawLeftCornerShadow(g, x, y, flip, backgroundColor);
+    drawEdgeShadow(g, x, x + width, y, false, flip, backgroundColor);
+  }
+
+  private void drawLeftCornerShadow(Graphics g, int x, int y, boolean upper, Color backgroundColor) {
+    for (int i = 0; i < shadowBlendSize; i++) {
+      g.setColor(getShadowBlendColor(i, backgroundColor));
+      int x1 = x + shadowSize + shadowBlendSize - 1 - i;
+      int y1 = y + shadowSize - shadowBlendSize;
+
+      if (y1 > y)
+        drawLine(g, x1, y, x1, y1 - 1, upper);
+
+      drawLine(g, x1, y1, x + shadowSize + shadowBlendSize - 1, y + shadowSize - shadowBlendSize + i, upper);
+    }
+  }
+
+  private void drawRightCornerShadow(Graphics g, int x, int y, boolean flip, Color backgroundColor) {
+    g.setColor(getShadowColor(backgroundColor));
+
+    for (int i = 0; i < shadowSize - shadowBlendSize; i++) {
+      drawLine(g, x + i, y, x + i, y + shadowSize - shadowBlendSize, flip);
+    }
+
+    for (int i = 0; i < shadowBlendSize; i++) {
+      g.setColor(getShadowBlendColor(i, backgroundColor));
+      int d = shadowSize - shadowBlendSize + i;
+      drawLine(g, x + d, y, x + d, y + shadowSize - shadowBlendSize, flip);
+      drawLine(g, x, y + d, x + shadowSize - shadowBlendSize, y + d, flip);
+      drawLine(g, x + d, y + shadowSize - shadowBlendSize, x + shadowSize - shadowBlendSize, y + d, flip);
+    }
+  }
+
+  private void drawEdgeShadow(Graphics g, int startX, int endX, int y, boolean cornerStart, boolean vertical,
+                              Color backgroundColor) {
+    if (startX + (cornerStart ? 0 : shadowSize + shadowBlendSize) >= endX)
+      return;
+
+    g.setColor(getShadowColor(backgroundColor));
+
+    for (int i = 0; i < shadowSize - shadowBlendSize; i++) {
+      drawLine(g,
+               startX + (cornerStart ? i + (vertical ? 1 : 0) : shadowSize + shadowBlendSize),
+               y + i,
+               endX - 1,
+               y + i,
+               vertical);
+    }
+
+    for (int i = 0; i < shadowBlendSize; i++) {
+      g.setColor(getShadowBlendColor(i, backgroundColor));
+      int d = shadowSize - shadowBlendSize + i;
+      drawLine(g,
+               startX + (cornerStart ? d + (vertical ? 1 : 0) : shadowSize + shadowBlendSize),
+               y + d,
+               endX - 1,
+               y + d,
+               vertical);
+    }
+  }
+
+/*  private void drawShadowLine(Graphics g, int startX, int endX, int y, boolean vertical, Color backgroundColor) {
+    if (startX >= endX)
+      return;
+
+    g.setColor(getShadowColor(backgroundColor));
+
+    for (int i = 0; i < shadowSize - shadowBlendSize; i++) {
+      drawLine(g, startX, y + i, endX - 1, y + i, vertical);
+    }
+
+    for (int i = 0; i < shadowBlendSize; i++) {
+      g.setColor(getShadowBlendColor(i, backgroundColor));
+      int d = shadowSize - shadowBlendSize + i;
+      drawLine(g, startX, y + d, endX - 1, y + d, vertical);
+    }
+  }
+*/
+  private static void drawLine(Graphics g, int x1, int y1, int x2, int y2, boolean flip) {
+    if (flip)
+      GraphicsUtil.drawOptimizedLine(g, y1, x1, y2, x2);
+    else
+      GraphicsUtil.drawOptimizedLine(g, x1, y1, x2, y2);
+  }
+
+
+  private Color getShadowBlendColor(int offset, Color backgroundColor) {
+    return backgroundColor == null ?
+           new Color(shadowColor.getRed(),
+                     shadowColor.getGreen(),
+                     shadowColor.getBlue(),
+                     (int) (255F * shadowStrength * (shadowBlendSize - offset) / (shadowBlendSize + 1))) :
+           ColorUtil.blend(backgroundColor,
+                           shadowColor,
+                           shadowStrength * (shadowBlendSize - offset) / (shadowBlendSize + 1));
+  }
+
+  private Color getShadowColor(Color backgroundColor) {
+    return backgroundColor == null ?
+           new Color(shadowColor.getRed(),
+                     shadowColor.getGreen(),
+                     shadowColor.getBlue(),
+                     (int) (255F * shadowStrength)) :
+           ColorUtil.blend(backgroundColor,
+                           shadowColor,
+                           shadowStrength);
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/internal/SlopedTabLineBorder.java b/src/net/infonode/tabbedpanel/internal/SlopedTabLineBorder.java
new file mode 100644
index 0000000..7eb28fb
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/internal/SlopedTabLineBorder.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SlopedTabLineBorder.java,v 1.3 2005/02/16 11:28:14 jesper Exp $
+
+package net.infonode.tabbedpanel.internal;
+
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.UIManagerColorProvider;
+import net.infonode.gui.shaped.border.AbstractPolygonBorder;
+
+import java.awt.*;
+
+/**
+ * @author johan
+ * @since 1.2.0
+ */
+public class SlopedTabLineBorder extends AbstractPolygonBorder {
+  private static final long serialVersionUID = 1;
+
+  private static final int[][][] corners = {
+    {
+      {0, 0},
+      {-1, 0, 0, -1},
+      {-2, 0, -1, -1, 0, -1},
+      {-4, 0, -3, -1, -2, -1, -1, -2, -1, -3, 0, -4},
+    },
+    {
+      {0, 0},
+      {0, 0},
+      {0, 1, 1, 1, 2, 0},
+      {0, 4, 1, 3, 1, 2, 2, 1, 3, 1, 4, 0},
+    },
+    {
+      {0, 0},
+      {0, 0},
+      {-2, 0, -1, 1, 0, 1},
+      {-4, 0, -3, 1, -2, 1, -1, 2, -1, 3, 0, 4}
+    },
+    {
+      {0, 0},
+      {0, -1, 1, 0},
+      {0, -1, 1, -1, 2, 0},
+      {0, -4, 1, -3, 1, -2, 2, -1, 3, -1, 4, 0},
+    },
+    {
+      {0, 0},
+      {0, 0},
+      {0, 2, 1, 1, 1, 0},
+      {0, 4, 1, 3, 1, 2, 2, 1, 3, 1, 4, 0},
+    },
+    {
+      {0, 0},
+      {0, 0},
+      {-1, 0, -1, 1, 0, 2},
+      {-4, 0, -3, 1, -2, 1, -1, 2, -1, 3, 0, 4},
+    },
+  };
+
+  private boolean drawBottomLine;
+  private float leftSlope;
+  private float rightSlope;
+  private boolean bottomLeftRounded;
+  private boolean topLeftRounded;
+  private boolean topRightRounded;
+  private boolean bottomRightRounded;
+  private int leftHeight;
+  private int rightHeight;
+
+  private static int[] xCoords = new int[100];
+  private static int[] yCoords = new int[100];
+  private static int x;
+  private static int y;
+  private static int index;
+
+  public SlopedTabLineBorder() {
+    this(0, 1);
+  }
+
+  public SlopedTabLineBorder(float leftSlope, float rightSlope) {
+    this(leftSlope, rightSlope, 22, 22);
+  }
+
+  public SlopedTabLineBorder(float leftSlope, float rightSlope, int leftHeight, int rightHeight) {
+    this(leftSlope, rightSlope, leftHeight, rightHeight, false, false, false, false);
+  }
+
+  public SlopedTabLineBorder(float leftSlope, float rightSlope,
+                             boolean bottomLeftRounded, boolean topLeftRounded, boolean topRightRounded,
+                             boolean bottomRightRounded) {
+    this(leftSlope,
+         rightSlope,
+         22,
+         22,
+         bottomLeftRounded,
+         topLeftRounded,
+         topRightRounded,
+         bottomRightRounded);
+  }
+
+  public SlopedTabLineBorder(float leftSlope, float rightSlope, int leftHeight, int rightHeight,
+                             boolean bottomLeftRounded, boolean topLeftRounded, boolean topRightRounded,
+                             boolean bottomRightRounded) {
+    this(UIManagerColorProvider.TABBED_PANE_DARK_SHADOW,
+         UIManagerColorProvider.TABBED_PANE_HIGHLIGHT,
+         false,
+         leftSlope,
+         rightSlope,
+         leftHeight,
+         rightHeight,
+         bottomLeftRounded,
+         topLeftRounded,
+         topRightRounded,
+         bottomRightRounded);
+  }
+
+  public SlopedTabLineBorder(ColorProvider lineColor, ColorProvider highlightColor, boolean drawBottomLine,
+                             float leftSlope, float rightSlope, int leftHeight, int rightHeight,
+                             boolean bottomLeftRounded, boolean topLeftRounded, boolean topRightRounded,
+                             boolean bottomRightRounded) {
+    super(lineColor, highlightColor);
+    this.drawBottomLine = drawBottomLine;
+    this.leftHeight = leftHeight;
+    this.rightHeight = rightHeight;
+    this.leftSlope = leftSlope;
+    this.rightSlope = rightSlope;
+    this.bottomLeftRounded = bottomLeftRounded;
+    this.topLeftRounded = topLeftRounded;
+    this.topRightRounded = topRightRounded;
+    this.bottomRightRounded = bottomRightRounded;
+  }
+
+  protected boolean lineIsDrawn(int index, Polygon polygon) {
+    return drawBottomLine || index < polygon.npoints - 1;
+  }
+
+  protected Insets getShapedBorderInsets(Component c) {
+    return new Insets(1,
+                      (isBottomLeftRounded(c) ? 4 : 1) +
+                      (topLeftRounded ? (leftSlope <= 0.5f ? 4 : 1) : 0) +
+                      (int) (leftSlope * leftHeight),
+                      drawBottomLine ? 1 : 0,
+                      (bottomRightRounded ? 4 : 1) +
+                      (topRightRounded ? (rightSlope <= 0.5f ? 4 : 1) : 0) +
+                      (int) (rightSlope * rightHeight));
+  }
+
+  protected boolean isBottomLeftRounded(Component c) {
+    return bottomLeftRounded;
+  }
+
+  private static int[] getCorner(int type, float slope, boolean rounded) {
+    return corners[type][!rounded ? 0 :
+                         type < 4 ?
+                         (slope >= 2f ? 1 : slope >= 1f ? 2 : 3) :
+                         (slope <= 0.5f ? 1 : slope <= 1f ? 2 : 3)];
+  }
+
+  private static void addPoint(int px, int py) {
+    xCoords[index] = px;
+    yCoords[index++] = py;
+    x = px;
+    y = py;
+  }
+
+  private static void addCorner(int px, int py, int[] c) {
+    for (int i = 0; i < c.length; i++) {
+      addPoint(px + c[i++], py + c[i]);
+    }
+  }
+
+  private static int getStartY(int[] corner) {
+    return corner[1];
+  }
+
+  private static int getEndY(int[] corner) {
+    return corner[corner.length - 1];
+  }
+
+  protected Polygon createPolygon(Component c, int width, int height) {
+    boolean bottomLeftRounded = isBottomLeftRounded(c);
+    int leftX = (int) (leftHeight * leftSlope + (bottomLeftRounded ? 4 : 0));
+    int rightX = width - 1 - (int) (rightHeight * rightSlope + (bottomRightRounded ? 4 : 0));
+    int bottomY = height - (drawBottomLine ? 1 : 0);
+
+    int[] topLeft = getCorner(1, leftSlope, topLeftRounded);
+    int[] topRight = getCorner(2, rightSlope, topRightRounded);
+    int[] bottomLeft = getCorner(0, leftSlope, bottomLeftRounded);
+    int[] bottomRight = getCorner(3, rightSlope, bottomRightRounded);
+
+    index = 0;
+    y = bottomY;
+    int dy = height - getStartY(topLeft) + getEndY(bottomLeft);
+
+    if (dy <= leftHeight) {
+      x = leftX - (int) (dy * leftSlope);
+      addCorner(x, y, bottomLeft);
+    }
+    else {
+      x = leftX - (int) (leftHeight * leftSlope);
+      addCorner(x, y, getCorner(0, 0, bottomLeftRounded));
+      addPoint(x, getStartY(topLeft) + leftHeight);
+    }
+
+    addCorner(leftX, 0, topLeft);
+    addCorner(rightX, 0, topRight);
+    dy = height - getEndY(topRight) + getStartY(bottomRight);
+
+    if (dy <= rightHeight) {
+      addCorner((int) (rightX + dy * rightSlope), bottomY, bottomRight);
+    }
+    else {
+      addPoint((int) (rightX + rightSlope * rightHeight), getEndY(topRight) + rightHeight);
+      addCorner(x, bottomY, getCorner(3, 0, bottomRightRounded));
+    }
+
+    return new Polygon(xCoords, yCoords, index);
+  }
+
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/internal/TabDropDownList.java b/src/net/infonode/tabbedpanel/internal/TabDropDownList.java
new file mode 100644
index 0000000..16c19b9
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/internal/TabDropDownList.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabDropDownList.java,v 1.18 2005/12/04 13:46:05 jesper Exp $
+
+package net.infonode.tabbedpanel.internal;
+
+import net.infonode.gui.PopupList;
+import net.infonode.gui.PopupListListener;
+import net.infonode.gui.TextIconListCellRenderer;
+import net.infonode.tabbedpanel.*;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+/**
+ * @author Bjorn Lind
+ * @version $Revision: 1.18 $ $Date: 2005/12/04 13:46:05 $
+ * @since ITP 1.1.0
+ */
+public class TabDropDownList extends PopupList {
+  private TabbedPanel tabbedPanel;
+  private TextIconListCellRenderer cellRenderer;
+
+  private TabListener tabListener = new TabAdapter() {
+    public void tabAdded(TabEvent event) {
+      if (event.getTab().getTabbedPanel().getTabCount() == 2)
+        setVisible(true);
+    }
+
+    public void tabRemoved(TabRemovedEvent event) {
+      if (event.getTabbedPanel().getTabCount() == 1)
+        setVisible(false);
+    }
+  };
+
+  public TabDropDownList(final TabbedPanel tabbedPanel, AbstractButton button) {
+    super(button);
+    this.tabbedPanel = tabbedPanel;
+
+    addPopupListListener(new PopupListListener() {
+      public void willBecomeVisible(PopupList l) {
+        int numTabs = tabbedPanel.getTabCount();
+        Tab[] tabs = new Tab[numTabs];
+        for (int i = 0; i < numTabs; i++) {
+          tabs[i] = tabbedPanel.getTabAt(i);
+        }
+        cellRenderer.calculateMaximumIconWidth(tabs);
+        getList().setListData(tabs);
+        getList().setSelectedValue(tabbedPanel.getSelectedTab(), true);
+      }
+    });
+
+    addListSelectionListener(new ListSelectionListener() {
+      public void valueChanged(ListSelectionEvent e) {
+        if (!e.getValueIsAdjusting())
+          tabbedPanel.setSelectedTab((Tab) getList().getSelectedValue());
+      }
+    });
+
+    if (tabbedPanel.getProperties().getTabDropDownListVisiblePolicy() ==
+        TabDropDownListVisiblePolicy.MORE_THAN_ONE_TAB) {
+      tabbedPanel.addTabListener(tabListener);
+      setVisible(tabbedPanel.getTabCount() > 1);
+    }
+
+    cellRenderer = new TextIconListCellRenderer(getList().getCellRenderer());
+    getList().setCellRenderer(cellRenderer);
+    setOpaque(false);
+  }
+
+  public void dispose() {
+    tabbedPanel.removeTabListener(tabListener);
+  }
+
+  public void updateUI() {
+    super.updateUI();
+
+    if (cellRenderer != null) {
+      ListCellRenderer renderer = (ListCellRenderer) UIManager.get("List.cellRenderer");
+      if (renderer == null)
+        renderer = new DefaultListCellRenderer();
+      cellRenderer.setRenderer(renderer);
+    }
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/internal/TabbedHoverUtil.java b/src/net/infonode/tabbedpanel/internal/TabbedHoverUtil.java
new file mode 100644
index 0000000..7409a51
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/internal/TabbedHoverUtil.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedHoverUtil.java,v 1.8 2005/12/04 13:46:05 jesper Exp $
+
+package net.infonode.tabbedpanel.internal;
+
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedPanelContentPanel;
+import net.infonode.tabbedpanel.TabbedPanelHoverPolicy;
+
+import java.awt.*;
+import java.util.ArrayList;
+
+/**
+ * @author johan
+ */
+public class TabbedHoverUtil {
+
+  public static boolean isDeepestHoverableTabbedPanel(ArrayList enterables, TabbedPanel tp) {
+    Component c = (Component) enterables.get(0);
+    while (c != null) {
+      TabbedPanel tp2 = null;
+
+      if (c instanceof TabbedPanel)
+        tp2 = (TabbedPanel) c;
+
+      if (c instanceof TabbedPanelContentPanel)
+        tp2 = ((TabbedPanelContentPanel) c).getTabbedPanel();
+
+      if (tp2 != null)
+        if (tp2 == tp)
+          return true;
+        else if (tp2.getProperties().getHoverPolicy() != TabbedPanelHoverPolicy.ALWAYS_AND_EXCLUDE)
+          return false;
+      /*if (c instanceof TabbedPanel)
+        if (c == tp)
+          return true;
+        else
+          return false;
+
+      if (c instanceof TabbedPanelContentPanel)
+        if (((TabbedPanelContentPanel) c).getTabbedPanel() == tp)
+          return true;
+        else
+          return false;*/
+
+      c = c.getParent();
+    }
+
+    return true;
+  }
+
+  public static boolean hasVisibleTabbedPanelChild(Component c) {
+    if (c instanceof TabbedPanel && ((TabbedPanel) c).getProperties().getHoverPolicy() != TabbedPanelHoverPolicy.ALWAYS_AND_EXCLUDE)
+      return true;
+
+    if (c instanceof Container) {
+      Container container = ((Container) c);
+      for (int i = 0; i < container.getComponentCount(); i++) {
+        if (container.getComponent(i).isVisible() && hasVisibleTabbedPanelChild(container.getComponent(i)))
+          return true;
+      }
+    }
+
+    return false;
+  }
+
+  public static boolean acceptTabbedPanelHover(TabbedPanelHoverPolicy policy,
+                                               ArrayList enterables,
+                                               TabbedPanel tp,
+                                               Component c) {
+    if (policy == TabbedPanelHoverPolicy.NO_HOVERED_CHILD)
+      return isDeepestHoverableTabbedPanel(enterables, tp);
+
+    if (policy == TabbedPanelHoverPolicy.NEVER)
+      return false;
+
+    if (policy == TabbedPanelHoverPolicy.ALWAYS || policy == TabbedPanelHoverPolicy.ALWAYS_AND_EXCLUDE)
+      return true;
+
+    if (policy == TabbedPanelHoverPolicy.ONLY_WHEN_DEEPEST && c != null)
+      return !hasVisibleTabbedPanelChild(c);
+
+    return false;
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/internal/TwoColoredLineBorder.java b/src/net/infonode/tabbedpanel/internal/TwoColoredLineBorder.java
new file mode 100644
index 0000000..a388610
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/internal/TwoColoredLineBorder.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TwoColoredLineBorder.java,v 1.7 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.internal;
+
+import net.infonode.gui.GraphicsUtil;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.shaped.border.RoundedCornerBorder;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedUtils;
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+import net.infonode.util.Direction;
+
+import java.awt.*;
+
+/**
+ * TwoColoredLineBorder draws a 1 pixel wide line. The border can have
+ * one color for the top and left line and another color for the bottom
+ * and right line.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ * @see TitledTab
+ * @see TabbedPanel
+ * @since ITP 1.2.0
+ */
+public class TwoColoredLineBorder extends RoundedCornerBorder {
+  private static final long serialVersionUID = 1;
+
+  private ColorProvider topLeftColor;
+  private ColorProvider bottomRightColor;
+  private boolean roundCorners;
+  private boolean open;
+
+  /**
+   * Constructs a TwoColoredLineBorder
+   *
+   * @param topLeftColor     the colorprovider for the top and left lines
+   * @param bottomRightColor the colorprovider for the bottom and right lines
+   * @param roundCorners     true for round corners on the side facing away from the tab area
+   * @param open             true for no border on the side towards the tab area
+   */
+  public TwoColoredLineBorder(ColorProvider topLeftColor,
+                              ColorProvider bottomRightColor,
+                              boolean roundCorners,
+                              boolean open) {
+    super(topLeftColor,
+          null,
+          roundCorners ? 2 : 0,
+          roundCorners ? 2 : 0,
+          roundCorners && !open ? 2 : 0,
+          roundCorners && !open ? 2 : 0,
+          true,
+          true,
+          !open,
+          true);
+    this.topLeftColor = topLeftColor;
+    this.bottomRightColor = bottomRightColor;
+    this.roundCorners = roundCorners;
+    this.open = open;
+  }
+
+  protected void paintPolygon(Component c, Graphics2D g, Polygon polygon, int width, int height) {
+    TabbedPanel tp = TabbedUtils.getParentTabbedPanel(c);
+    if (tp != null) {
+      Direction d = tp.getProperties().getTabAreaOrientation();
+
+      int i = 0;
+
+      Color c1 = topLeftColor.getColor();
+      Color c2 = bottomRightColor.getColor();
+
+      if (d == Direction.UP) {
+        g.setColor(c1);
+        while (i < (roundCorners ? 3 : 1)) {
+          GraphicsUtil.drawOptimizedLine(g,
+                                         polygon.xpoints[i],
+                                         polygon.ypoints[i],
+                                         polygon.xpoints[i + 1],
+                                         polygon.ypoints[i + 1]);
+          i++;
+        }
+
+        g.setColor(c2);
+        while (i < polygon.npoints - 1) {
+          GraphicsUtil.drawOptimizedLine(g,
+                                         polygon.xpoints[i],
+                                         polygon.ypoints[i],
+                                         polygon.xpoints[i + 1],
+                                         polygon.ypoints[i + 1]);
+          i++;
+        }
+
+        g.setColor(c1);
+        GraphicsUtil.drawOptimizedLine(g,
+                                       polygon.xpoints[i],
+                                       polygon.ypoints[i],
+                                       polygon.xpoints[0],
+                                       polygon.ypoints[0]);
+
+      }
+      else if (d == Direction.RIGHT) {
+        g.setColor(c2);
+        while (i < polygon.npoints - (open ? 2 : roundCorners ? 3 : 2)) {
+          GraphicsUtil.drawOptimizedLine(g,
+                                         polygon.xpoints[i],
+                                         polygon.ypoints[i],
+                                         polygon.xpoints[i + 1],
+                                         polygon.ypoints[i + 1]);
+          i++;
+        }
+
+        g.setColor(c1);
+        for (int k = i - 1; k < polygon.npoints - 2; k++) {
+          GraphicsUtil.drawOptimizedLine(g,
+                                         polygon.xpoints[i],
+                                         polygon.ypoints[i],
+                                         polygon.xpoints[i + 1],
+                                         polygon.ypoints[i + 1]);
+          i++;
+        }
+        GraphicsUtil.drawOptimizedLine(g,
+                                       polygon.xpoints[i],
+                                       polygon.ypoints[i],
+                                       polygon.xpoints[0],
+                                       polygon.ypoints[0]);
+
+      }
+      else if (d == Direction.DOWN) {
+        g.setColor(c2);
+        while (i < (roundCorners ? 5 : 2)) {
+          GraphicsUtil.drawOptimizedLine(g,
+                                         polygon.xpoints[i],
+                                         polygon.ypoints[i],
+                                         polygon.xpoints[i + 1],
+                                         polygon.ypoints[i + 1]);
+          i++;
+        }
+
+        g.setColor(c1);
+        while (i < polygon.npoints - 1) {
+          GraphicsUtil.drawOptimizedLine(g,
+                                         polygon.xpoints[i],
+                                         polygon.ypoints[i],
+                                         polygon.xpoints[i + 1],
+                                         polygon.ypoints[i + 1]);
+          i++;
+        }
+
+        GraphicsUtil.drawOptimizedLine(g,
+                                       polygon.xpoints[i],
+                                       polygon.ypoints[i],
+                                       polygon.xpoints[0],
+                                       polygon.ypoints[0]);
+      }
+      else {
+        g.setColor(c1);
+        while (i < (roundCorners ? 3 : 1)) {
+          GraphicsUtil.drawOptimizedLine(g,
+                                         polygon.xpoints[i],
+                                         polygon.ypoints[i],
+                                         polygon.xpoints[i + 1],
+                                         polygon.ypoints[i + 1]);
+          i++;
+        }
+
+        g.setColor(c2);
+        while (i < polygon.npoints - 1) {
+          GraphicsUtil.drawOptimizedLine(g,
+                                         polygon.xpoints[i],
+                                         polygon.ypoints[i],
+                                         polygon.xpoints[i + 1],
+                                         polygon.ypoints[i + 1]);
+          i++;
+        }
+
+        g.setColor(c1);
+
+        GraphicsUtil.drawOptimizedLine(g,
+                                       polygon.xpoints[i],
+                                       polygon.ypoints[i],
+                                       polygon.xpoints[0],
+                                       polygon.ypoints[0]);
+      }
+    }
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/package.html b/src/net/infonode/tabbedpanel/package.html
new file mode 100644
index 0000000..95d0920
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Core classes for creating and using tabbed panels and tabs
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/theme/BlueHighlightTheme.java b/src/net/infonode/tabbedpanel/theme/BlueHighlightTheme.java
new file mode 100644
index 0000000..55a3db4
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/BlueHighlightTheme.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: BlueHighlightTheme.java,v 1.24 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.theme;
+
+import net.infonode.gui.border.HighlightBorder;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.border.TabAreaLineBorder;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.LineBorder;
+import java.awt.*;
+
+/**
+ * A theme with dark borders and blue (or custom color) background for the
+ * highlighted state.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.24 $
+ */
+public class BlueHighlightTheme extends TabbedPanelTitledTabTheme {
+  private TitledTabProperties tabProperties = new TitledTabProperties();
+  private TabbedPanelProperties tabbedPanelProperties = new TabbedPanelProperties();
+
+  /**
+   * Constructs a BlueHighlightTheme
+   */
+  public BlueHighlightTheme() {
+    this(new Color(90, 120, 200));
+  }
+
+  /**
+   * Constructs a BlueHighlightTheme
+   *
+   * @param backgroundColor the background color for the highlighted tab
+   */
+  public BlueHighlightTheme(Color backgroundColor) {
+    tabProperties.getHighlightedProperties().getComponentProperties()
+        .setForegroundColor(Color.WHITE)
+        .setBackgroundColor(backgroundColor)
+        .setBorder(new TabAreaLineBorder(Color.BLACK));
+
+    tabbedPanelProperties.getContentPanelProperties().getComponentProperties().setBorder(new CompoundBorder(
+        new LineBorder(Color.BLACK),
+        new HighlightBorder()));
+    tabbedPanelProperties.getTabAreaComponentsProperties().getComponentProperties().setBorder(
+        new TabAreaLineBorder(Color.BLACK));
+  }
+
+  /**
+   * Gets the name for this theme
+   *
+   * @return the name
+   * @since ITP 1.1.0
+   */
+  public String getName() {
+    return "Blue Highlight Theme";
+  }
+
+  /**
+   * Gets the TitledTabProperties for this theme
+   *
+   * @return the TitledTabProperties
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return tabProperties;
+  }
+
+  /**
+   * Gets the TabbedPanelProperties for this theme
+   *
+   * @return the TabbedPanelProperties
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return tabbedPanelProperties;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/theme/ClassicTheme.java b/src/net/infonode/tabbedpanel/theme/ClassicTheme.java
new file mode 100644
index 0000000..8b83a30
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/ClassicTheme.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ClassicTheme.java,v 1.15 2008/04/04 12:43:22 jesper Exp $
+
+package net.infonode.tabbedpanel.theme;
+
+import net.infonode.gui.GraphicsUtil;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.UIManagerColorProvider;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.TabbedUIDefaults;
+import net.infonode.tabbedpanel.TabbedUtils;
+import net.infonode.tabbedpanel.border.OpenContentBorder;
+import net.infonode.tabbedpanel.internal.TwoColoredLineBorder;
+import net.infonode.tabbedpanel.titledtab.TitledTabBorderSizePolicy;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+import net.infonode.util.Direction;
+
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import java.awt.*;
+
+/**
+ * A theme with a "classic" look and with round edges for the titled tabs.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.15 $
+ * @since ITP 1.2.0
+ */
+public class ClassicTheme extends TabbedPanelTitledTabTheme {
+  private TabbedPanelProperties tabbedPanelProperties = new TabbedPanelProperties();
+  private TitledTabProperties titledTabProperties = new TitledTabProperties();
+  private ColorProvider highlightColor = UIManagerColorProvider.TABBED_PANE_HIGHLIGHT;
+  private ColorProvider shadowColor = UIManagerColorProvider.TABBED_PANE_SHADOW;
+  private ColorProvider darkShadow = UIManagerColorProvider.TABBED_PANE_DARK_SHADOW;
+  private int raised;
+
+  private Border shadowBorder;
+
+  /**
+   * Constructs a default Classic Theme
+   */
+  public ClassicTheme() {
+    this(2);
+  }
+
+  /**
+   * Constructs a Classic Theme
+   *
+   * @param raised number of pixels for the highlight raised effect
+   */
+  public ClassicTheme(final int raised) {
+    this.raised = raised;
+
+    shadowBorder = new Border() {
+      public boolean isBorderOpaque() {
+        return false;
+      }
+
+      public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+        TabbedPanel tp = TabbedUtils.getParentTabbedPanel(c);
+        if (tp != null) {
+          Direction d = tp.getProperties().getTabAreaOrientation();
+          g.setColor(shadowColor.getColor());
+
+          if (d == Direction.UP || d == Direction.DOWN)
+            GraphicsUtil.drawOptimizedLine(g, x + width - 1, y, x + width - 1, y + height - 1);
+          else
+            GraphicsUtil.drawOptimizedLine(g, x, y + height - 1, x + width - 1, y + height - 1);
+        }
+      }
+
+      public Insets getBorderInsets(Component c) {
+        TabbedPanel tp = TabbedUtils.getParentTabbedPanel(c);
+        if (tp != null) {
+          Direction d = tp.getProperties().getTabAreaOrientation();
+          return new Insets(0,
+                            0,
+                            (d == Direction.LEFT || d == Direction.RIGHT) ? 1 : 0,
+                            (d == Direction.UP || d == Direction.DOWN) ? 1 : 0);
+        }
+        return new Insets(0, 0, 0, 0);
+      }
+    };
+
+    Border contentBorder = new CompoundBorder(
+        new OpenContentBorder(highlightColor, darkShadow, null, 1),
+        new OpenContentBorder(null, shadowColor, null, 1));
+    tabbedPanelProperties.getContentPanelProperties().getComponentProperties().setBorder(contentBorder).setInsets(
+        new Insets(1, 1, 1, 1));
+
+    tabbedPanelProperties.setTabSpacing(-raised - 1).setShadowEnabled(false);
+
+    tabbedPanelProperties.getTabAreaComponentsProperties().getComponentProperties().setBorder(
+        doCreateTabBorder(false, false, true, true));
+
+
+    Border normalBorder = createInsetsTabBorder(true, true, false);
+    Border highlightBorder = doCreateTabBorder(true, true, true, false);
+    Insets normalInsets = new Insets(0, 3, 0, 3);
+    Insets highlightInsets = new Insets(0,
+                                        (raised + 1) / 2 + (((raised + 1) & 1) == 1 ? 1 : 0) + normalInsets.left,
+                                        1,
+                                        (raised + 1) / 2 + normalInsets.right);
+
+
+    titledTabProperties.setHighlightedRaised(raised).setBorderSizePolicy(TitledTabBorderSizePolicy.INDIVIDUAL_SIZE);
+    titledTabProperties.getNormalProperties().getComponentProperties().setBorder(normalBorder).setInsets(normalInsets);
+    titledTabProperties.getHighlightedProperties().getComponentProperties().setBorder(highlightBorder).setInsets(
+        highlightInsets);
+  }
+
+  /**
+   * Gets the name for this theme
+   *
+   * @return the name
+   */
+  public String getName() {
+    return "Classic Theme";
+  }
+
+  /**
+   * Gets the TabbedPanelProperties for this theme
+   *
+   * @return the TabbedPanelProperties
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return tabbedPanelProperties;
+  }
+
+  /**
+   * Gets the TitledTabProperties for this theme
+   *
+   * @return the TitledTabProperties
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return titledTabProperties;
+  }
+
+  /**
+   * Creates a tab border with extra insets border
+   *
+   * @param roundEdges true for round edges
+   * @param open       true for open
+   * @param highlight  true for highlight
+   * @return the created border
+   */
+  public Border createInsetsTabBorder(boolean roundEdges, boolean open, boolean highlight) {
+    Border insetsBorder = new Border() {
+      public boolean isBorderOpaque() {
+        return false;
+      }
+
+      public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+      }
+
+      public Insets getBorderInsets(Component c) {
+        TabbedPanel tp = TabbedUtils.getParentTabbedPanel(c);
+        if (tp != null) {
+          Direction d = tp.getProperties().getTabAreaOrientation();
+          return new Insets(d == Direction.RIGHT || d == Direction.LEFT ? raised : 0, d == Direction.UP ||
+                                                                                      d == Direction.DOWN ?
+                                                                                      raised : 0, d == Direction.RIGHT ||
+                                                                                                  d == Direction.LEFT
+                                                                                                  ? 1
+                                                                                                  : 0, d ==
+                                                                                                       Direction.UP ||
+                                                                                                       d ==
+                                                                                                       Direction.DOWN ?
+                                                                                                       1 : 0);
+        }
+        return new Insets(0, 0, 0, 0);
+      }
+    };
+    return new CompoundBorder(insetsBorder, doCreateTabBorder(roundEdges, open, highlight, true));
+  }
+
+  /**
+   * Creates a tab border without extra insets border
+   *
+   * @param roundEdges true for round edges
+   * @param open       true for open
+   * @param highlight  true for highlight
+   * @return the created border
+   */
+  public Border createTabBorder(boolean roundEdges, boolean open, boolean highlight) {
+    return doCreateTabBorder(roundEdges, open, true, highlight);
+  }
+
+  private ColorProvider createNormalHighlightColorProvider() {
+    return new ColorProvider() {
+      public Color getColor() {
+        Color highlightBackground = TabbedUIDefaults.getHighlightedStateBackground();
+        highlightBackground = new Color(highlightBackground.getRed() == 0 ? 1 : highlightBackground.getRed(), highlightBackground.getGreen() ==
+                                                                                                              0 ?
+                                                                                                              1 :
+                                                                                                              highlightBackground.getGreen(),
+                                        highlightBackground.getBlue() == 0 ? 1 : highlightBackground.getBlue());
+
+        Color normalBackgroundColor = TabbedUIDefaults.getNormalStateBackground();
+
+        Color color = highlightColor.getColor();
+
+        int r = (int) (normalBackgroundColor.getRed() *
+                       ((float) (color.getRed()) / (float) (highlightBackground.getRed())));
+        int g = (int) (normalBackgroundColor.getGreen() *
+                       ((float) (color.getGreen()) / (float) (highlightBackground.getGreen())));
+        int b = (int) (normalBackgroundColor.getBlue() *
+                       ((float) (color.getBlue()) / (float) (highlightBackground.getBlue())));
+
+        r = (r + color.getRed()) / 2;
+        g = (g + color.getGreen()) / 2;
+        b = (b + color.getBlue()) / 2;
+
+        return new Color(r > 255 ? 255 : r, g > 255 ? 255 : g, b > 255 ? 255 : b);
+      }
+
+      public Color getColor(Component component) {
+        return getColor();
+      }
+    };
+  }
+
+  private Border doCreateTabBorder(boolean roundEdges, boolean open, boolean highlight, final boolean equalInset) {
+    return new CompoundBorder(new TwoColoredLineBorder(
+        highlight ? highlightColor : createNormalHighlightColorProvider(), darkShadow, roundEdges, open) {
+      protected Insets getShapedBorderInsets(Component c) {
+        return equalInset ? new Insets(1, 1, 1, 1) : super.getShapedBorderInsets(c);
+      }
+    }, shadowBorder);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/theme/DefaultTheme.java b/src/net/infonode/tabbedpanel/theme/DefaultTheme.java
new file mode 100644
index 0000000..0893acd
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/DefaultTheme.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: DefaultTheme.java,v 1.4 2004/09/28 15:07:29 jesper Exp $
+
+package net.infonode.tabbedpanel.theme;
+
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+/**
+ * A default theme that only contains empty tabbed panel properties and titled
+ * tab properties object i.e. they don't contain any properties that are set
+ * with any values. This theme is only a convenience theme so that a default
+ * theme can be added and removed just like any other theme.
+ *
+ * @author johan
+ * @version $Revision: 1.4 $
+ * @since ITP 1.1.0
+ */
+public class DefaultTheme extends TabbedPanelTitledTabTheme {
+  private TitledTabProperties tabProperties = new TitledTabProperties();
+  private TabbedPanelProperties tabbedPanelProperties = new TabbedPanelProperties();
+
+  /**
+   * Constructs a default theme
+   */
+  public DefaultTheme() {
+  }
+
+  /**
+   * Gets the name for this theme
+   *
+   * @return the name
+   */
+  public String getName() {
+    return "Default Theme";
+  }
+
+  /**
+   * Gets the TabbedPanelProperties for this theme
+   *
+   * @return the TabbedPanelProperties
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return tabbedPanelProperties;
+  }
+
+  /**
+   * Gets the TitledTabProperties for this theme
+   *
+   * @return the TitledTabProperties
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return tabProperties;
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/theme/GradientTheme.java b/src/net/infonode/tabbedpanel/theme/GradientTheme.java
new file mode 100644
index 0000000..50768f2
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/GradientTheme.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: GradientTheme.java,v 1.22 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel.theme;
+
+import net.infonode.gui.Colors;
+import net.infonode.gui.colorprovider.ColorMultiplier;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.ColorProviderUtil;
+import net.infonode.gui.colorprovider.UIManagerColorProvider;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.border.GradientTabAreaBorder;
+import net.infonode.tabbedpanel.border.OpenContentBorder;
+import net.infonode.tabbedpanel.border.TabAreaLineBorder;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import java.awt.*;
+
+/**
+ * A theme that draws gradient tab backgrounds.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.22 $
+ * @since ITP 1.1.0
+ */
+public class GradientTheme extends TabbedPanelTitledTabTheme {
+  private static final float HUE = Colors.ROYAL_BLUE_HUE;
+  private static final float SATURATION = 0.06f;
+  private static final float BRIGHTNESS = 0.72f;
+
+  /**
+   * The tab area background color used if no color is specified in the constructor.
+   */
+  public static final Color DEFAULT_TAB_AREA_BACKGROUND_COLOR = Color.getHSBColor(HUE, SATURATION, BRIGHTNESS);
+
+  private static final Border HIGHLIGHTED_TAB_GRADIENT_BORDER = new GradientTabAreaBorder(Color.WHITE);
+
+  private static final Border TAB_AREA_COMPONENTS_GRADIENT_BORDER =
+      new GradientTabAreaBorder(new ColorMultiplier(UIManagerColorProvider.CONTROL_COLOR, 0.88),
+                                UIManagerColorProvider.CONTROL_COLOR);
+
+  private boolean opaqueTabArea;
+  private boolean shadowEnabled;
+  private Color borderColor;
+  private Color tabAreaBackgroundColor;
+  private Border normalTabGradientBorder;
+  private TitledTabProperties titledTabProperties = new TitledTabProperties();
+  private TabbedPanelProperties tabbedPanelProperties = new TabbedPanelProperties();
+
+  /**
+   * Creates a default theme with transparent tab area and shadows.
+   */
+  public GradientTheme() {
+    this(false, true);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param opaqueTabArea if true a gradient background is drawn for the tab area, otherwise it's transparent
+   * @param shadowEnabled if true the shadow is enabled
+   */
+  public GradientTheme(boolean opaqueTabArea, boolean shadowEnabled) {
+    this(opaqueTabArea, shadowEnabled, null);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param opaqueTabArea if true a gradient background is drawn for the tab area, otherwise it's transparent
+   * @param shadowEnabled if true the shadow is enabled
+   * @param borderColor   the border color, null means default border color
+   */
+  public GradientTheme(boolean opaqueTabArea, boolean shadowEnabled, Color borderColor) {
+    this(opaqueTabArea, shadowEnabled, borderColor, DEFAULT_TAB_AREA_BACKGROUND_COLOR);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param opaqueTabArea          if true a gradient background is drawn for the tab area, otherwise it's transparent
+   * @param shadowEnabled          if true the shadow is enabled
+   * @param borderColor            the border color, null means default border color
+   * @param tabAreaBackgroundColor the background color for the tab area and normal tabs, null means use the default tab
+   *                               background
+   */
+  public GradientTheme(boolean opaqueTabArea, boolean shadowEnabled, Color borderColor, Color tabAreaBackgroundColor) {
+    this.opaqueTabArea = opaqueTabArea;
+    this.shadowEnabled = shadowEnabled;
+    this.borderColor = borderColor;
+    this.tabAreaBackgroundColor = tabAreaBackgroundColor;
+
+    ColorProvider cp = ColorProviderUtil.getColorProvider(tabAreaBackgroundColor,
+                                                          UIManagerColorProvider.TABBED_PANE_BACKGROUND);
+
+    normalTabGradientBorder = new GradientTabAreaBorder(cp, new ColorMultiplier(cp, 1.1));
+    initTabbedPanelProperties();
+    initTitledTabProperties();
+  }
+
+  /**
+   * Gets the name for this theme
+   *
+   * @return the name
+   */
+  public String getName() {
+    return "Gradient Theme" + (opaqueTabArea ? " - Opaque Tab Area" : "");
+  }
+
+  private void initTabbedPanelProperties() {
+
+    tabbedPanelProperties.getContentPanelProperties().getComponentProperties()
+        .setInsets(new Insets(3, 3, 3, 3))
+        .setBorder(new OpenContentBorder(borderColor, opaqueTabArea ? 0 : 1));
+
+    tabbedPanelProperties
+        .setShadowEnabled(shadowEnabled)
+        .setPaintTabAreaShadow(opaqueTabArea)
+        .setTabSpacing(opaqueTabArea ? 0 : -1);
+
+    if (opaqueTabArea) {
+      if (tabAreaBackgroundColor != null)
+        tabbedPanelProperties.getTabAreaProperties().getComponentProperties()
+            .setBackgroundColor(tabAreaBackgroundColor);
+
+      tabbedPanelProperties.getTabAreaProperties().getComponentProperties()
+          .setBorder(new CompoundBorder(new TabAreaLineBorder(borderColor), normalTabGradientBorder));
+    }
+
+    tabbedPanelProperties.getTabAreaComponentsProperties()
+        .setStretchEnabled(opaqueTabArea)
+
+        .getComponentProperties()
+        .setBorder(new CompoundBorder(new TabAreaLineBorder(opaqueTabArea ? null : borderColor,
+                                                            !opaqueTabArea,
+                                                            true,
+                                                            !opaqueTabArea,
+                                                            true),
+                                      TAB_AREA_COMPONENTS_GRADIENT_BORDER))
+        .setInsets(opaqueTabArea ? new Insets(0, 3, 0, 3) : new Insets(1, 3, 1, 3));
+  }
+
+  private void initTitledTabProperties() {
+    if (opaqueTabArea)
+      titledTabProperties.setHighlightedRaised(0);
+
+    titledTabProperties.getNormalProperties()
+        .getComponentProperties()
+        .setBorder(opaqueTabArea ?
+                   (Border) new TabAreaLineBorder(false, false, true, true) :
+                   new CompoundBorder(new TabAreaLineBorder(), normalTabGradientBorder));
+
+    if (opaqueTabArea)
+      titledTabProperties.getNormalProperties()
+          .getComponentProperties().setBackgroundColor(null);
+
+    if (!opaqueTabArea && tabAreaBackgroundColor != null)
+      titledTabProperties.getNormalProperties()
+          .getComponentProperties().setBackgroundColor(tabAreaBackgroundColor);
+
+    titledTabProperties.getHighlightedProperties()
+        .setIconVisible(true)
+
+        .getComponentProperties()
+        .setBorder(new CompoundBorder(opaqueTabArea ?
+                                      (Border) new TabAreaLineBorder(false, false, true, true) :
+                                      new TabAreaLineBorder(borderColor),
+                                      HIGHLIGHTED_TAB_GRADIENT_BORDER));
+  }
+
+  public TitledTabProperties getTitledTabProperties() {
+    return titledTabProperties;
+  }
+
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return tabbedPanelProperties;
+  }
+
+  /**
+   * Returns the gradient border for the highlighted tab.
+   *
+   * @return the gradient border for the highlighted tab
+   */
+  public Border getHighlightedTabGradientBorder() {
+    return HIGHLIGHTED_TAB_GRADIENT_BORDER;
+  }
+
+  /**
+   * Returns the gradient border for the tab area components.
+   *
+   * @return the gradient border for the tab area components
+   */
+  public Border getTabAreaComponentsGradientBorder() {
+    return TAB_AREA_COMPONENTS_GRADIENT_BORDER;
+  }
+
+  /**
+   * Returns the gradient border for the normal tab or the tab area if it's opaque.
+   *
+   * @return the gradient border for the normal tab or the tab area if it's opaque
+   */
+  public Border getNormalTabGradientBorder() {
+    return normalTabGradientBorder;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/theme/LookAndFeelTheme.java b/src/net/infonode/tabbedpanel/theme/LookAndFeelTheme.java
new file mode 100644
index 0000000..0eab2b7
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/LookAndFeelTheme.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: LookAndFeelTheme.java,v 1.20 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.theme;
+
+import net.infonode.gui.DimensionProvider;
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.componentpainter.ComponentPainter;
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.properties.propertymap.PropertyMapManager;
+import net.infonode.tabbedpanel.*;
+import net.infonode.tabbedpanel.theme.internal.laftheme.PaneUI;
+import net.infonode.tabbedpanel.theme.internal.laftheme.PaneUIListener;
+import net.infonode.tabbedpanel.titledtab.TitledTab;
+import net.infonode.tabbedpanel.titledtab.TitledTabBorderSizePolicy;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+import net.infonode.tabbedpanel.titledtab.TitledTabSizePolicy;
+import net.infonode.util.Direction;
+
+import javax.swing.border.Border;
+import java.awt.*;
+
+/**
+ * <p>
+ * An <strong>experimental</strong> theme that tries to replicate the look of
+ * the active look and feel. This may or may not work depending on the look and
+ * feel used.
+ * </p>
+ *
+ * <p>
+ * This is a theme that tries to replicate the JTabbedPane's look using the
+ * active look and feel. The theme uses a heavyweight AWT component internally
+ * so the {@link #dispose()} method must be called when the theme is no longer
+ * needed, otherwise the native resources will not be disposed.
+ * </p>
+ *
+ * <p>
+ * The theme uses the hover mechanism so that tab hover effects can be
+ * replicated.
+ * </p>
+ *
+ * <p>
+ * <strong>This theme is considered to be experimental and is not guaranteed to
+ * be an exact replica of the active look and feel. It is also not guaranteed to
+ * work together with the active look and feel. The theme may be changed,
+ * removed etc in future versions. No support is given for the theme. This theme
+ * doesn't work well with Aqua Look and Feel on Macintosh.</strong>
+ * </p>
+ *
+ * @author johan
+ * @version $Revision: 1.20 $
+ * @since ITP 1.4.0
+ */
+public class LookAndFeelTheme extends TabbedPanelTitledTabTheme {
+  private static TabbedPanelProperties tpProps = new TabbedPanelProperties();
+
+  private static TitledTabProperties tabProps = new TitledTabProperties();
+
+  private TabbedPanelProperties themeTpProps = new TabbedPanelProperties();
+
+  private TitledTabProperties themeTabProps = new TitledTabProperties();
+
+  private static int themeCounter = 0;
+
+  private static PaneUI ui;
+
+  private boolean disposed = false;
+
+  /**
+   * Constructs a Look and Feel Theme
+   */
+  public LookAndFeelTheme() {
+    if (themeCounter == 0) {
+      ui = new PaneUI(new PaneUIListener() {
+        public void updating() {
+        }
+
+        public void updated() {
+          initTheme();
+        }
+
+      });
+
+      ui.init();
+    }
+
+    themeCounter++;
+
+    themeTpProps.addSuperObject(tpProps);
+    themeTabProps.addSuperObject(tabProps);
+  }
+
+  /**
+   * Gets the name for this theme
+   *
+   * @return the name
+   */
+  public String getName() {
+    return "Look and Feel Theme";
+  }
+
+  /**
+   * Gets the TabbedPanelProperties for this theme
+   *
+   * @return the TabbedPanelProperties
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return themeTpProps;
+  }
+
+  /**
+   * Gets the TitledTabProperties for this theme
+   *
+   * @return the TitledTabProperties
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return themeTabProps;
+  }
+
+  /**
+   * <p>
+   * Disposes this theme.
+   * </p>
+   *
+   * <p>
+   * This method must be called in order to dispose the heavyweight AWT
+   * component used internally.
+   * </p>
+   */
+  public void dispose() {
+    if (!disposed) {
+      disposed = true;
+
+      themeCounter--;
+
+      if (themeCounter == 0) {
+        ui.dispose();
+
+        PropertyMapManager.runBatch(new Runnable() {
+          public void run() {
+            tpProps.getMap().clear(true);
+            tabProps.getMap().clear(true);
+          }
+        });
+      }
+    }
+  }
+
+  private void initTheme() {
+    PropertyMapManager.runBatch(new Runnable() {
+      public void run() {
+        tpProps.getMap().clear(true);
+        tabProps.getMap().clear(true);
+
+        {
+          tpProps.setShadowEnabled(false).setTabSpacing(ui.getTabSpacing()).setTabScrollingOffset(ui.getScrollOffset())
+              .setEnsureSelectedTabVisible(true);
+          tpProps.getTabAreaComponentsProperties().getComponentProperties().setBorder(null).setInsets(
+              InsetsUtil.EMPTY_INSETS);
+
+          tpProps.getTabAreaProperties().getComponentProperties().setInsets(InsetsUtil.EMPTY_INSETS).setBorder(new Border() {
+            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+            }
+
+            public Insets getBorderInsets(Component c) {
+              TabbedPanel tp = TabbedUtils.getParentTabbedPanel(c);
+              return tp.isTabAreaVisible() ?
+                     ui.getTabAreaInsets(tp.getProperties().getTabAreaOrientation()) : InsetsUtil.EMPTY_INSETS;
+            }
+
+            public boolean isBorderOpaque() {
+              return false;
+            }
+
+          });
+
+          tpProps.getTabAreaComponentsProperties().getShapedPanelProperties().setOpaque(ui.isTabAreaComponentsOpaque());
+
+          tpProps.getTabAreaProperties().getShapedPanelProperties().setOpaque(ui.isTabAreaOpaque())
+              .setComponentPainter(new ComponentPainter() {
+
+                public void paint(Component component, Graphics g, int x, int y, int width, int height) {
+                }
+
+                public void paint(Component component, Graphics g, int x, int y, int width, int height, Direction direction,
+                                  boolean horizontalFlip, boolean verticalFlip) {
+                  ui.paintTabArea(TabbedUtils.getParentTabbedPanel(component), g, x, y, width, height);
+                }
+
+                public boolean isOpaque(Component component) {
+                  return false;
+                }
+
+                public Color getColor(Component component) {
+                  return null;
+                }
+              });
+
+          tpProps.getContentPanelProperties().getShapedPanelProperties().setOpaque(ui.isContentOpaque())
+              .setComponentPainter(new ComponentPainter() {
+                public void paint(Component component, Graphics g, int x, int y, int width, int height) {
+                }
+
+                public void paint(Component component, Graphics g, int x, int y, int width, int height, Direction direction,
+                                  boolean horizontalFlip, boolean verticalFlip) {
+                  TabbedPanelContentPanel p = TabbedUtils.getParentTabbedPanelContentPanel(component);
+                  ui.paintContentArea(p, g, x, y, width, height);
+                }
+
+                public boolean isOpaque(Component component) {
+                  return false;
+                }
+
+                public Color getColor(Component component) {
+                  return null;
+                }
+              });
+
+          tpProps.getContentPanelProperties().getComponentProperties().setInsets(InsetsUtil.EMPTY_INSETS).setBorder(new Border() {
+            public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+            }
+
+            public Insets getBorderInsets(Component c) {
+              TabbedPanel tp = TabbedUtils.getParentTabbedPanelContentPanel(c).getTabbedPanel();
+              return ui.getContentInsets(tp.getProperties().getTabAreaOrientation(), tp.isTabAreaVisible());
+            }
+
+            public boolean isBorderOpaque() {
+              return false;
+            }
+          });
+        }
+
+        {
+          tabProps.setSizePolicy(TitledTabSizePolicy.INDIVIDUAL_SIZE).setBorderSizePolicy(
+              TitledTabBorderSizePolicy.EQUAL_SIZE)
+              .setHighlightedRaised(ui.getSelectedRaised(Direction.UP)).setFocusMarkerEnabled(false);
+
+          tabProps.getNormalProperties().setIconTextGap(ui.getTextIconGap()).setTextTitleComponentGap(
+              ui.getTextIconGap());
+
+          tabProps.getNormalProperties().getComponentProperties().setInsets(InsetsUtil.EMPTY_INSETS)
+              .setBorder(createTabInsetsBorder(false)).setFont(ui.getFont());
+          tabProps.getHighlightedProperties().getComponentProperties().setBorder(createTabInsetsBorder(true));
+
+          tabProps.getDisabledProperties().getComponentProperties().setBorder(createTabInsetsBorder(false));
+
+          tabProps.getNormalProperties().getShapedPanelProperties().setOpaque(false).setComponentPainter(null);
+
+          tabProps.setMinimumSizeProvider(new DimensionProvider() {
+            public Dimension getDimension(Component c) {
+              return ui.getTabExternalMinSize(
+                  TabbedUtils.getParentTab(c).getTabbedPanel().getProperties().getTabAreaOrientation());
+            }
+          });
+
+          tabProps.setHoverListener(new HoverListener() {
+            public void mouseEntered(HoverEvent event) {
+              ui.setHoveredTab((Tab) event.getSource());
+            }
+
+            public void mouseExited(HoverEvent event) {
+              ui.setHoveredTab(null);
+            }
+          });
+        }
+      }
+    });
+  }
+
+  private Border createTabInsetsBorder(final boolean selected) {
+    return new Border() {
+      public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+      }
+
+      public Insets getBorderInsets(Component c) {
+        TitledTab tab = (TitledTab) TabbedUtils.getParentTab(c);
+
+        if (tab.getTabbedPanel() == null)
+          return new Insets(0, 0, 0, 0);
+
+        Direction areaOrientation = tab.getTabbedPanel().getProperties().getTabAreaOrientation();
+        Direction tabDirection = selected ? tab.getProperties().getHighlightedProperties().getDirection() : tab.getProperties()
+            .getNormalProperties().getDirection();
+
+        return selected ?
+               ui.getSelectedTabInsets(areaOrientation, tabDirection) :
+               ui.getNormalTabInsets(areaOrientation, tabDirection);
+      }
+
+      public boolean isBorderOpaque() {
+        return false;
+      }
+    };
+  }
+
+  public Color getBorderColor(Direction d) {
+    return ui.getContentTabAreaBorderColor(d);
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/theme/ShapedGradientTheme.java b/src/net/infonode/tabbedpanel/theme/ShapedGradientTheme.java
new file mode 100644
index 0000000..ff6a156
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/ShapedGradientTheme.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ShapedGradientTheme.java,v 1.14 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.theme;
+
+import net.infonode.gui.HighlightPainter;
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.colorprovider.*;
+import net.infonode.gui.componentpainter.GradientComponentPainter;
+import net.infonode.tabbedpanel.Tab;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.TabbedUtils;
+import net.infonode.tabbedpanel.border.OpenContentBorder;
+import net.infonode.tabbedpanel.internal.SlopedTabLineBorder;
+import net.infonode.tabbedpanel.titledtab.TitledTabBorderSizePolicy;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+import net.infonode.tabbedpanel.titledtab.TitledTabStateProperties;
+
+import javax.swing.border.Border;
+import java.awt.*;
+
+/**
+ * A theme with tabs with rounded edges, gradient backgrounds and support for
+ * slopes on left/right side of tab.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.14 $
+ * @since ITP 1.2.0
+ */
+public class ShapedGradientTheme extends TabbedPanelTitledTabTheme {
+  private static final int CORNER_INSET = 3;
+
+  private ColorProvider highlightColor;
+  private ColorProvider lineColor;
+  private ColorProvider controlColor;
+  private ColorProvider darkControlColor;
+  private ColorProvider alternateHighlight;
+  private int leftSlopeHeight;
+  private int rightSlopeHeight;
+
+  private static class TabBorder extends SlopedTabLineBorder {
+    private boolean bottomLeftRounded;
+    private boolean isNormal;
+    private boolean hasLeftSlope;
+    private int raised;
+    private int cornerInset;
+
+    TabBorder(ColorProvider lineColor, ColorProvider highlightColor,
+              float leftSlope, float rightSlope, int leftHeight, int rightHeight,
+              boolean bottomLeftRounded, boolean topLeftRounded, boolean topRightRounded,
+              boolean bottomRightRounded, boolean isNormal, boolean highlightBottomLeftRounded, int raised) {
+      super(lineColor, highlightColor, false, leftSlope, rightSlope, leftHeight,
+            rightHeight, isNormal ? false : bottomLeftRounded, topLeftRounded, topRightRounded, bottomRightRounded);
+
+      this.bottomLeftRounded = bottomLeftRounded;
+      this.isNormal = isNormal;
+      this.raised = raised;
+      hasLeftSlope = leftSlope > 0;
+
+      cornerInset = highlightBottomLeftRounded ? CORNER_INSET : 0;
+    }
+
+    protected Polygon createPolygon(Component c, int width, int height) {
+      Polygon p = super.createPolygon(c, width, height);
+      if (isNormal) {
+        int leftX = width / 2;
+        boolean first = isFirst(c);
+        for (int i = 0; i < p.npoints; i++) {
+          if (p.xpoints[i] < leftX)
+            p.xpoints[i] = p.xpoints[i] + raised + (first ? 0 : cornerInset);
+          else
+            p.xpoints[i] = p.xpoints[i] - raised - cornerInset;
+        }
+      }
+
+      return p;
+    }
+
+    protected Insets getShapedBorderInsets(Component c) {
+      Insets i = super.getShapedBorderInsets(c);
+
+      Insets addInsets = new Insets(0, 0, 0, 1 + raised);
+      //Insets addInsets = new Insets(0, raised, 0, raised);
+      if (isNormal && !isFirst(c))
+        addInsets.left = addInsets.left + cornerInset;
+      if (!isNormal)
+        addInsets.right = addInsets.right - cornerInset;
+
+      return InsetsUtil.add(i, addInsets);
+    }
+
+    private boolean isFirst(Component c) {
+      if (!hasLeftSlope) {
+        Tab tab = TabbedUtils.getParentTab(c);
+        if (tab != null && tab.getTabbedPanel() != null) {
+          return tab.getTabbedPanel().getTabAt(0) == tab;
+        }
+      }
+
+      return false;
+    }
+
+    protected boolean isBottomLeftRounded(Component c) {
+      return isFirst(c) ? false : bottomLeftRounded;
+    }
+  }
+
+  private TabbedPanelProperties tabbedPanelProperties = new TabbedPanelProperties();
+  private TitledTabProperties titledTabProperties = new TitledTabProperties();
+
+  /**
+   * Creates a default theme with sloped border on the right side of the tab
+   * and with colors based on the active look and feel
+   */
+  public ShapedGradientTheme() {
+    this(0f, 0.5f);
+  }
+
+  /**
+   * Creates a theme with the given slopes on the left and right side of the tab
+   * and with colors based on the active look and feel
+   *
+   * @param leftSlope  leaning of left slope defined as left slope width divided by left slope height
+   * @param rightSlope leaning of right slope defined as right slope width divided by right slope height
+   */
+  public ShapedGradientTheme(float leftSlope, float rightSlope) {
+    this(leftSlope,
+         rightSlope,
+         UIManagerColorProvider.TABBED_PANE_DARK_SHADOW,
+         UIManagerColorProvider.TABBED_PANE_HIGHLIGHT);
+  }
+
+  /**
+   * Creates a theme with the given slopes on the left and right side of the tab
+   * and with the given colors
+   *
+   * @param leftSlope      leaning of left slope defined as left slope width divided
+   *                       by left slope height
+   * @param rightSlope     leaning of right slope defined as right slope width divided
+   *                       by right slope height
+   * @param lineColor      color provider for the lines
+   * @param highlightColor color provider for the highlighting, null for no highlighting
+   */
+  public ShapedGradientTheme(float leftSlope, float rightSlope, ColorProvider lineColor, ColorProvider highlightColor) {
+    this(leftSlope, rightSlope, 25, lineColor, highlightColor);
+  }
+
+  /**
+   * Creates a theme with the given slopes on the left and right side of
+   * the tab and with the given colors
+   *
+   * @param leftSlope      leaning of left slope defined as left slope width divided
+   *                       by left slope height
+   * @param rightSlope     leaning of right slope defined as right slope width divided
+   *                       by right slope height
+   * @param slopeHeight    slope height in pixels, used when estimating slope width
+   * @param lineColor      color provider for the lines
+   * @param highlightColor color provider for the highlighting, null for no highlighting
+   */
+  public ShapedGradientTheme(float leftSlope,
+                             float rightSlope,
+                             int slopeHeight,
+                             ColorProvider lineColor,
+                             ColorProvider highlightColor) {
+    this.leftSlopeHeight = slopeHeight;
+    this.rightSlopeHeight = slopeHeight;
+    this.highlightColor = highlightColor;
+    this.lineColor = lineColor;
+    this.controlColor = UIManagerColorProvider.CONTROL_COLOR;
+    darkControlColor = UIManagerColorProvider.TABBED_PANE_BACKGROUND;
+    alternateHighlight = highlightColor != null ?
+                         new ColorBlender(highlightColor, controlColor, 0.3f) :
+                         (ColorProvider) new ColorMultiplier(controlColor, 1.2);
+
+    GradientComponentPainter blendedComponentPainter = new GradientComponentPainter(alternateHighlight,
+                                                                                    alternateHighlight,
+                                                                                    controlColor,
+                                                                                    controlColor);
+
+    int leftSlopeWidth = (int) (leftSlope * leftSlopeHeight);
+    int rightSlopeWidth = (int) (rightSlope * rightSlopeHeight);
+    int highlightedRaised = 2;
+
+    boolean bottomLeftRounded = true;
+    boolean topLeftRounded = true;
+    boolean topRightRounded = true;
+    boolean bottomRightRounded = true;
+
+    titledTabProperties.setHighlightedRaised(highlightedRaised).setBorderSizePolicy(
+        TitledTabBorderSizePolicy.EQUAL_SIZE);
+
+    TitledTabStateProperties normalState = titledTabProperties.getNormalProperties();
+    TitledTabStateProperties highlightState = titledTabProperties.getHighlightedProperties();
+    TitledTabStateProperties disabledState = titledTabProperties.getDisabledProperties();
+
+    Border normalBorder = new TabBorder(lineColor, null, leftSlope, rightSlope, leftSlopeHeight, rightSlopeHeight, false, topLeftRounded, topRightRounded,
+                                        false, true, true, highlightedRaised);
+    Border highlightBorder = new TabBorder(lineColor,
+                                           highlightColor,
+                                           leftSlope,
+                                           rightSlope,
+                                           leftSlopeHeight,
+                                           rightSlopeHeight,
+                                           bottomLeftRounded,
+                                           topLeftRounded,
+                                           topRightRounded,
+                                           bottomRightRounded,
+                                           false,
+                                           true,
+                                           highlightedRaised);
+
+    normalState.getComponentProperties().setBorder(normalBorder).setInsets(new Insets(0, 0, 0, 0));
+    highlightState.getComponentProperties().setBorder(highlightBorder);
+
+    ColorProvider darkControlColor1 = new ColorMultiplier(darkControlColor, 1.1);
+    ColorProvider darkControlColor2 = new ColorMultiplier(darkControlColor, 0.92);
+    GradientComponentPainter normalComponentPainter = new GradientComponentPainter(darkControlColor1,
+                                                                                   darkControlColor1,
+                                                                                   darkControlColor2,
+                                                                                   darkControlColor2);
+    normalState.getShapedPanelProperties().setOpaque(false).setComponentPainter(normalComponentPainter);
+    disabledState.getShapedPanelProperties().setComponentPainter(normalComponentPainter);
+
+    if (highlightColor == null)
+      highlightState.getShapedPanelProperties().setComponentPainter(blendedComponentPainter);
+    else
+      highlightState.getShapedPanelProperties().setComponentPainter(
+          new GradientComponentPainter(highlightColor, highlightColor, controlColor, controlColor));
+
+    Insets insets = normalBorder.getBorderInsets(null);
+    int tabSpacing = 1 + insets.left + insets.right - (topLeftRounded ? CORNER_INSET : 0) - (topRightRounded ?
+                                                                                             CORNER_INSET : 0) -
+                     (int) (0.2 * (leftSlopeWidth + rightSlopeWidth));
+    tabbedPanelProperties.setTabSpacing(-tabSpacing).setShadowEnabled(false);
+
+    tabbedPanelProperties.getTabAreaComponentsProperties().getComponentProperties().setBorder(new SlopedTabLineBorder(
+        lineColor,
+        highlightColor,
+        false,
+        0f,
+        0f,
+        0,
+        0,
+        false,
+        topLeftRounded,
+        topRightRounded,
+        false)).setInsets(new Insets(0, 0, 0, 0));
+
+    tabbedPanelProperties.getTabAreaComponentsProperties().getShapedPanelProperties().setComponentPainter(
+        blendedComponentPainter);
+
+    tabbedPanelProperties.getTabAreaProperties().getShapedPanelProperties().setOpaque(false);
+
+    tabbedPanelProperties.getContentPanelProperties().getComponentProperties()
+        .setBorder(new OpenContentBorder(lineColor, lineColor,
+                                         highlightColor == null ? null : new ColorBlender(highlightColor,
+                                                                                          BackgroundPainterColorProvider.INSTANCE,
+                                                                                          HighlightPainter.getBlendFactor(
+                                                                                              1, 0)),
+                                         1));
+  }
+
+  /**
+   * Gets the theme name
+   *
+   * @return name for this theme
+   */
+  public String getName() {
+    return "Shaped Gradient Theme";
+  }
+
+  /**
+   * Gets the TabbedPanelProperties for this theme
+   *
+   * @return the TabbedPanelProperties
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return tabbedPanelProperties;
+  }
+
+  /**
+   * Gets the TitledTabProperties for this theme
+   *
+   * @return the TitledTabProperties
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return titledTabProperties;
+  }
+
+  /**
+   * Gets the line color provider
+   *
+   * @return the line color provider
+   */
+  public ColorProvider getLineColor() {
+    return lineColor;
+  }
+
+  /**
+   * Gets the highlight color provider
+   *
+   * @return the highlight color provider, null if no highlight
+   */
+  public ColorProvider getHighlightColor() {
+    return highlightColor;
+  }
+
+  /**
+   * Gets the alternate highlight color provider used for tab area
+   * components gradient background and highlighted tab background
+   * (when no highlight color is specified)
+   *
+   * @return the alternate highlight color provider
+   */
+  public ColorProvider getAlternateHighlightColor() {
+    return alternateHighlight;
+  }
+
+  /**
+   * Gets the control background color
+   *
+   * @return the control background color provider
+   */
+  public ColorProvider getControlColor() {
+    return controlColor;
+  }
+
+  /**
+   * Gets the dark control background color used for gradient for
+   * normal tab and disabled tab
+   *
+   * @return the dark control background color provider
+   */
+  public ColorProvider getDarkControlColor() {
+    return darkControlColor;
+  }
+
+  /**
+   * Creates a tab border
+   *
+   * @param lineColor                  line color provider
+   * @param highlightColor             highlight color provider, null for no highlight
+   * @param leftSlope                  left slope
+   * @param rightSlope                 right slope
+   * @param bottomLeftRounded          true if bottom left should be rounded
+   * @param topLeftRounded             true if top left should be rounded
+   * @param topRightRounded            true if top right should be rounded
+   * @param bottomRightRounded         true if bottom right should be rounded
+   * @param isNormal                   true if this is a normal rendered border
+   * @param highlightBottomLeftRounded true if highlight has bottom left rounded
+   * @param raised                     raised
+   * @return the created border
+   */
+  public Border createTabBorder(ColorProvider lineColor,
+                                ColorProvider highlightColor,
+                                float leftSlope,
+                                float rightSlope,
+                                boolean bottomLeftRounded,
+                                boolean topLeftRounded,
+                                boolean topRightRounded,
+                                boolean bottomRightRounded,
+                                boolean isNormal,
+                                boolean highlightBottomLeftRounded,
+                                int raised) {
+    return new TabBorder(lineColor,
+                         highlightColor,
+                         leftSlope,
+                         rightSlope,
+                         25,
+                         25,
+                         bottomLeftRounded,
+                         topLeftRounded,
+                         topRightRounded,
+                         bottomRightRounded,
+                         isNormal,
+                         highlightBottomLeftRounded,
+                         raised);
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/theme/SmallFlatTheme.java b/src/net/infonode/tabbedpanel/theme/SmallFlatTheme.java
new file mode 100644
index 0000000..9db4b0f
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/SmallFlatTheme.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SmallFlatTheme.java,v 1.26 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.theme;
+
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.border.OpenContentBorder;
+import net.infonode.tabbedpanel.border.TabAreaLineBorder;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+import java.awt.*;
+
+/**
+ * A theme with small fonts and flat look
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.26 $
+ */
+public class SmallFlatTheme extends TabbedPanelTitledTabTheme {
+  private TitledTabProperties tabProperties = new TitledTabProperties();
+  private TabbedPanelProperties tabbedPanelProperties = new TabbedPanelProperties();
+
+  /**
+   * Constructs a SmallFlatTheme
+   */
+  public SmallFlatTheme() {
+    TitledTabProperties tabDefaultProp = TitledTabProperties.getDefaultProperties();
+
+    TabAreaLineBorder border = new TabAreaLineBorder();
+    Font font = tabDefaultProp.getNormalProperties().getComponentProperties().getFont();
+    if (font != null)
+      font = font.deriveFont((float) 9);
+    tabProperties.getNormalProperties().getComponentProperties()
+        .setBorder(border)
+        .setFont(font)
+        .setInsets(new Insets(0, 4, 0, 4));
+    tabProperties.getHighlightedProperties().getComponentProperties()
+        .setBorder(border);
+    tabProperties.setHighlightedRaised(0);
+
+    tabbedPanelProperties
+        .getContentPanelProperties().getComponentProperties().setBorder(new OpenContentBorder());
+
+    tabbedPanelProperties.getTabAreaComponentsProperties().getComponentProperties().setBorder(new TabAreaLineBorder());
+  }
+
+  /**
+   * Gets the name for this theme
+   *
+   * @return the name
+   * @since ITP 1.1.0
+   */
+  public String getName() {
+    return "Small Flat Theme";
+  }
+
+  /**
+   * Gets the TitledTabProperties for this theme
+   *
+   * @return the TitledTabProperties
+   */
+  public TitledTabProperties getTitledTabProperties() {
+    return tabProperties;
+  }
+
+  /**
+   * Gets the TabbedPanelProperties for this theme
+   *
+   * @return the TabbedPanelProperties
+   */
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return tabbedPanelProperties;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/theme/SoftBlueIceTheme.java b/src/net/infonode/tabbedpanel/theme/SoftBlueIceTheme.java
new file mode 100644
index 0000000..39054bd
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/SoftBlueIceTheme.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SoftBlueIceTheme.java,v 1.17 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.theme;
+
+import net.infonode.gui.colorprovider.ColorBlender;
+import net.infonode.gui.colorprovider.ColorProvider;
+import net.infonode.gui.colorprovider.FixedColorProvider;
+import net.infonode.gui.componentpainter.ComponentPainter;
+import net.infonode.gui.componentpainter.FixedTransformComponentPainter;
+import net.infonode.gui.componentpainter.GradientComponentPainter;
+import net.infonode.gui.shaped.border.RoundedCornerBorder;
+import net.infonode.properties.base.Property;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+import net.infonode.util.ColorUtil;
+
+import javax.swing.border.Border;
+import java.awt.*;
+
+/**
+ * A light blue theme with gradients and rounded corners.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.17 $
+ * @since ITP 1.2.0
+ */
+public class SoftBlueIceTheme extends TabbedPanelTitledTabTheme {
+  public static final FixedColorProvider DEFAULT_DARK_COLOR = new FixedColorProvider(
+      ColorUtil.mult(new Color(160, 170, 190), 0.90));
+  public static final FixedColorProvider DEFAULT_LIGHT_COLOR = new FixedColorProvider(new Color(220, 230, 240));
+
+  private ColorProvider darkColor;
+  private ColorProvider lightColor;
+  private TabbedPanelProperties tabbedPanelProperties = new TabbedPanelProperties();
+  private TitledTabProperties titledTabProperties = new TitledTabProperties();
+
+  /**
+   * Creates a theme with default colors and rounded corners.
+   */
+  public SoftBlueIceTheme() {
+    this(DEFAULT_DARK_COLOR, DEFAULT_LIGHT_COLOR, 4);
+  }
+
+  /**
+   * Constructor.
+   *
+   * @param darkColor  the dark color used in gradients
+   * @param lightColor the light color used in gradients
+   * @param cornerType the amount of rounding to use for corners, 0-4
+   */
+  public SoftBlueIceTheme(ColorProvider darkColor, ColorProvider lightColor, int cornerType) {
+    this.darkColor = darkColor;
+    this.lightColor = lightColor;
+
+    ColorProvider light = lightColor;
+    ColorProvider dark = new ColorBlender(darkColor, lightColor, 0.3f);
+    ColorProvider dark2 = new ColorBlender(darkColor, FixedColorProvider.WHITE, 0.1f);
+    ColorProvider dark3 = darkColor;
+
+    Border roundedBorder = new RoundedCornerBorder(dark3,
+                                                   light,
+                                                   cornerType,
+                                                   cornerType,
+                                                   cornerType,
+                                                   cornerType,
+                                                   true,
+                                                   true,
+                                                   true,
+                                                   true);
+
+    Border tabNormalBorder = roundedBorder;
+
+    Border contentBorder = new RoundedCornerBorder(dark3,
+                                                   light,
+                                                   cornerType,
+                                                   cornerType,
+                                                   cornerType,
+                                                   cornerType,
+                                                   false,
+                                                   true,
+                                                   true,
+                                                   true);
+
+    ComponentPainter areaPainter = new FixedTransformComponentPainter(
+        new GradientComponentPainter(dark2, light, light, dark2));
+    ComponentPainter contentPainter = new FixedTransformComponentPainter(
+        new GradientComponentPainter(light, dark2, dark2, light));
+    ComponentPainter highlightPainter = new FixedTransformComponentPainter(
+        new GradientComponentPainter(FixedColorProvider.WHITE, light, light, light));
+    ComponentPainter normalPainter = new FixedTransformComponentPainter(
+        new GradientComponentPainter(light, dark, dark, dark));
+
+    tabbedPanelProperties
+        .setPaintTabAreaShadow(true)
+        .setTabSpacing(2)
+        .setShadowEnabled(false);
+
+    tabbedPanelProperties.getTabAreaProperties()
+        .getComponentProperties()
+        .setBorder(roundedBorder)
+        .setInsets(new Insets(2, 2, 3, 3));
+
+    tabbedPanelProperties.getTabAreaProperties()
+        .getShapedPanelProperties()
+        .setClipChildren(true)
+        .setComponentPainter(areaPainter)
+        .setOpaque(false);
+
+    tabbedPanelProperties.getTabAreaComponentsProperties()
+        .setStretchEnabled(true)
+        .getComponentProperties()
+        .setBorder(null)
+        .setInsets(new Insets(0, 0, 0, 0));
+
+    tabbedPanelProperties.getTabAreaComponentsProperties()
+        .getShapedPanelProperties().setOpaque(false);
+
+    tabbedPanelProperties.getContentPanelProperties()
+        .getComponentProperties()
+        .setBorder(contentBorder)
+        .setInsets(new Insets(3, 3, 4, 4));
+
+    tabbedPanelProperties.getContentPanelProperties()
+        .getShapedPanelProperties()
+        .setComponentPainter(contentPainter)
+        .setClipChildren(true)
+        .setOpaque(false);
+
+    titledTabProperties.setHighlightedRaised(0);
+
+    Font font = titledTabProperties.getNormalProperties().getComponentProperties().getFont();
+
+    if (font != null)
+      font = font.deriveFont(Font.PLAIN).deriveFont(11f);
+
+    titledTabProperties.getNormalProperties()
+        .getComponentProperties()
+        .setBorder(tabNormalBorder)
+        .setInsets(new Insets(1, 4, 2, 5))
+        .setBackgroundColor(
+            titledTabProperties.getHighlightedProperties().getComponentProperties().getBackgroundColor())
+        .setFont(font);
+
+    titledTabProperties.getNormalProperties()
+        .getShapedPanelProperties()
+        .setComponentPainter(normalPainter).setOpaque(false);
+
+    Property[] linkedProperties = {ComponentProperties.BORDER, ComponentProperties.INSETS, ComponentProperties.FONT};
+
+    for (int i = 0; i < linkedProperties.length; i++) {
+      titledTabProperties.getHighlightedProperties().getComponentProperties().getMap().
+          createRelativeRef(linkedProperties[i],
+                            titledTabProperties.getNormalProperties().getComponentProperties().getMap(),
+                            linkedProperties[i]);
+    }
+
+    titledTabProperties.getHighlightedProperties()
+        .getShapedPanelProperties()
+        .setComponentPainter(highlightPainter);
+  }
+
+  public String getName() {
+    return "Soft Blue Ice Theme";
+  }
+
+  public TabbedPanelProperties getTabbedPanelProperties() {
+    return tabbedPanelProperties;
+  }
+
+  public TitledTabProperties getTitledTabProperties() {
+    return titledTabProperties;
+  }
+
+  /**
+   * Returns the dark gradient color.
+   *
+   * @return the dark gradient color
+   */
+  public ColorProvider getDarkColor() {
+    return darkColor;
+  }
+
+  /**
+   * Returns the light gradient color.
+   *
+   * @return the light gradient color
+   */
+  public ColorProvider getLightColor() {
+    return lightColor;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/theme/TabbedPanelTitledTabTheme.java b/src/net/infonode/tabbedpanel/theme/TabbedPanelTitledTabTheme.java
new file mode 100644
index 0000000..53af510
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/TabbedPanelTitledTabTheme.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabbedPanelTitledTabTheme.java,v 1.5 2004/09/28 15:07:29 jesper Exp $
+package net.infonode.tabbedpanel.theme;
+
+import net.infonode.tabbedpanel.TabbedPanelProperties;
+import net.infonode.tabbedpanel.titledtab.TitledTabProperties;
+
+/**
+ * Abstract class for a theme for a tabbed panel with titled tabs.
+ *
+ * @author johan
+ * @version $Revision: 1.5 $
+ * @since ITP 1.1.0
+ */
+public abstract class TabbedPanelTitledTabTheme {
+  /**
+   * Gets the name for this theme
+   *
+   * @return the name
+   */
+  abstract public String getName();
+
+  /**
+   * Gets the TabbedPanelProperties for this theme
+   *
+   * @return the TabbedPanelProperties
+   */
+  abstract public TabbedPanelProperties getTabbedPanelProperties();
+
+  /**
+   * Gets the TitledTabProperties for this theme
+   *
+   * @return the TitledTabProperties
+   */
+  abstract public TitledTabProperties getTitledTabProperties();
+}
diff --git a/src/net/infonode/tabbedpanel/theme/internal/laftheme/ComponentCache.java b/src/net/infonode/tabbedpanel/theme/internal/laftheme/ComponentCache.java
new file mode 100644
index 0000000..3bf1805
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/internal/laftheme/ComponentCache.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ComponentCache.java,v 1.4 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.theme.internal.laftheme;
+
+import javax.swing.*;
+import java.awt.*;
+import java.lang.ref.SoftReference;
+import java.util.ArrayList;
+
+public class ComponentCache {
+  private ArrayList cache = new ArrayList(10);
+
+  private int index = 0;
+
+  ComponentCache() {
+    for (int i = 0; i < 1; i++)
+      cache.add(new SoftReference(createComponent()));
+  }
+
+  void reset() {
+    index = 0;
+  }
+
+  Component getComponent() {
+    JComponent c = null;
+
+    if (index == cache.size()) {
+      c = createComponent();
+      cache.add(new SoftReference(c));
+    }
+    else {
+      c = (JComponent) ((SoftReference) cache.get(index)).get();
+      if (c == null) {
+        cache.remove(index);
+        c = createComponent();
+        cache.add(new SoftReference(c));
+      }
+    }
+
+    index++;
+    c.setOpaque(false);
+
+    return c;
+  }
+
+  private JComponent createComponent() {
+    return new JComponent() {
+    };
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneHandler.java b/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneHandler.java
new file mode 100644
index 0000000..612da17
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneHandler.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PaneHandler.java,v 1.7 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.theme.internal.laftheme;
+
+import net.infonode.gui.DynamicUIManager;
+import net.infonode.gui.DynamicUIManagerListener;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class PaneHandler {
+  private JFrame frame;
+
+  private PanePainter[] panePainters;
+
+  private PaneHandlerListener listener;
+
+  private DynamicUIManagerListener uiListener = new DynamicUIManagerListener() {
+
+    public void lookAndFeelChanged() {
+      doUpdate();
+    }
+
+    public void propertiesChanging() {
+      listener.updating();
+    }
+
+    public void propertiesChanged() {
+      doUpdate();
+    }
+
+    public void lookAndFeelChanging() {
+      listener.updating();
+    }
+
+  };
+
+  PaneHandler(PaneHandlerListener listener) {
+    this.listener = listener;
+
+    DynamicUIManager.getInstance().addPrioritizedListener(uiListener);
+
+    Direction[] directions = Direction.getDirections();
+    panePainters = new PanePainter[directions.length];
+
+    JPanel panel = new JPanel(null);
+    for (int i = 0; i < directions.length; i++) {
+      panePainters[i] = new PanePainter(directions[i]);
+      panel.add(panePainters[i]);
+      panePainters[i].setBounds(0, 0, 600, 600);
+    }
+
+    frame = new JFrame();
+    frame.getContentPane().add(panel, BorderLayout.CENTER);
+    frame.pack();
+  }
+
+  void dispose() {
+    if (frame != null) {
+      DynamicUIManager.getInstance().removePrioritizedListener(uiListener);
+      frame.removeAll();
+      frame.dispose();
+      frame = null;
+    }
+  }
+
+  PanePainter getPainter(Direction d) {
+    for (int i = 0; i < panePainters.length; i++)
+      if (panePainters[i].getDirection() == d)
+        return panePainters[i];
+
+    return null;
+  }
+
+  JFrame getFrame() {
+    return frame;
+  }
+
+  void update() {
+    listener.updating();
+
+    doUpdate();
+  }
+
+  private void doUpdate() {
+    SwingUtilities.updateComponentTreeUI(frame);
+
+    // SwingUtilities.invokeLater(new Runnable() {
+    // public void run() {
+    listener.updated();
+    // }
+    // });
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneHandlerListener.java b/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneHandlerListener.java
new file mode 100644
index 0000000..a609bf4
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneHandlerListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PaneHandlerListener.java,v 1.2 2005/12/04 12:56:44 jesper Exp $
+package net.infonode.tabbedpanel.theme.internal.laftheme;
+
+public interface PaneHandlerListener {
+  void updating();
+
+  void updated();
+}
diff --git a/src/net/infonode/tabbedpanel/theme/internal/laftheme/PanePainter.java b/src/net/infonode/tabbedpanel/theme/internal/laftheme/PanePainter.java
new file mode 100644
index 0000000..42e570d
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/internal/laftheme/PanePainter.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PanePainter.java,v 1.11 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.theme.internal.laftheme;
+
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.FocusEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+
+class PanePainter extends JTabbedPane {
+  private boolean mouseEntered = false;
+
+  private boolean focusActive = false;
+
+  private boolean useMouseEnterExit = false;
+
+  private Direction direction;
+
+  PanePainter(Direction d) {
+    setTabPlacement(d);
+    setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
+
+    useMouseEnterExit = UIManager.getLookAndFeel().getClass().getName().indexOf(".LiquidLookAndFeel") > -1;
+
+    if (!useMouseEnterExit)
+      super.processMouseEvent(
+          new MouseEvent(this, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), 0, 0, 0, 0, false));
+  }
+
+  void setTabAreaEntered(boolean entered) {
+    if (entered)
+      super.processMouseEvent(
+          new MouseEvent(this, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), 0, 0, 0, 0, false));
+    else
+      super.processMouseEvent(
+          new MouseEvent(this, MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), 0, -1, -1, 0, false));
+  }
+
+  private void setTabPlacement(Direction d) {
+    this.direction = d;
+
+    if (d == Direction.UP)
+      setTabPlacement(JTabbedPane.TOP);
+    else if (d == Direction.LEFT)
+      setTabPlacement(JTabbedPane.LEFT);
+    else if (d == Direction.RIGHT)
+      setTabPlacement(JTabbedPane.RIGHT);
+    else
+      setTabPlacement(JTabbedPane.BOTTOM);
+  }
+
+  void setMouseEntered(boolean entered) {
+    if (useMouseEnterExit) {
+      if (entered && !mouseEntered) {
+        super.processMouseEvent(
+            new MouseEvent(this, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), 0, 0, 0, 0, false));
+      }
+      else if (!entered && mouseEntered) {
+        super.processMouseEvent(
+            new MouseEvent(this, MouseEvent.MOUSE_EXITED, System.currentTimeMillis(), 0, -1, -1, 0, false));
+      }
+    }
+    else {
+      if (!entered && mouseEntered) {
+        super.processMouseMotionEvent(
+            new MouseEvent(this, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, -1, -1, 0, false));
+      }
+    }
+
+    mouseEntered = entered;
+  }
+
+  void setHoveredTab(int index) {
+    if (index > -1 && index < getTabCount()) {
+      Rectangle hoverBounds = getBoundsAt(index);
+      int xPos = hoverBounds.x + hoverBounds.width / 2;
+      int yPos = hoverBounds.y + hoverBounds.height / 2;
+
+      super.processMouseMotionEvent(
+          new MouseEvent(this, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, xPos, yPos, 0, false));
+    }
+  }
+
+  void setFocusActive(boolean active) {
+    if (active && !focusActive)
+      super.processFocusEvent(new FocusEvent(this, FocusEvent.FOCUS_GAINED));
+    else if (!active && focusActive)
+      super.processFocusEvent(new FocusEvent(this, FocusEvent.FOCUS_LOST));
+
+    focusActive = active;
+  }
+
+  Direction getDirection() {
+    return direction;
+  }
+
+  void doValidation() {
+    Component c = this;
+    while (c != null) {
+      c.invalidate();
+      c = c.getParent();
+    }
+    validate();
+  }
+
+  void removeAllTabs() {
+    removeAll();
+    doValidation();
+  }
+
+  public Font getFont() {
+    Font font = UIManager.getFont("TabbedPane.font");
+    return font == null ? super.getFont() : font;
+  }
+
+  public void updateUI() {
+    setBorder(null);
+    setBackground(null);
+    setForeground(null);
+    setOpaque(false);
+
+    super.updateUI();
+
+    setTabLayoutPolicy(WRAP_TAB_LAYOUT);
+
+    useMouseEnterExit = UIManager.getLookAndFeel().getClass().getName().indexOf(".LiquidLookAndFeel") > -1;
+
+    if (!useMouseEnterExit)
+      super.processMouseEvent(
+          new MouseEvent(this, MouseEvent.MOUSE_ENTERED, System.currentTimeMillis(), 0, 0, 0, 0, false));
+  }
+
+  public boolean hasFocus() {
+    return focusActive;
+  }
+
+  public void repaint() {
+  }
+
+  public void repaint(long tm, int x, int y, int width, int height) {
+  }
+
+  void paint(Graphics g, int tx, int ty) {
+    Rectangle clip = g.getClipBounds();
+
+    if (clip != null && clip.x == 0 && clip.y == 0 && clip.width == 0 && clip.height == 0) {
+      return;
+    }
+
+    g.translate(tx, ty);
+    update(g);
+    g.translate(-tx, -ty);
+  }
+
+  protected void processMouseEvent(MouseEvent e) {
+  }
+
+  protected void processMouseMotionEvent(MouseEvent e) {
+  }
+
+  protected void processFocusEvent(FocusEvent e) {
+  }
+
+  protected void processMouseWheelEvent(MouseWheelEvent e) {
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneUI.java b/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneUI.java
new file mode 100644
index 0000000..e512bc2
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneUI.java
@@ -0,0 +1,1201 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PaneUI.java,v 1.21 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.tabbedpanel.theme.internal.laftheme;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.awt.image.FilteredImageSource;
+import java.awt.image.RGBImageFilter;
+
+import javax.swing.Icon;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.draggable.DraggableComponentBox;
+import net.infonode.tabbedpanel.Tab;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.tabbedpanel.TabbedPanelContentPanel;
+import net.infonode.util.Direction;
+
+public class PaneUI {
+  private static final boolean PAINT_TAB_AREA = true;
+
+  private static final boolean PAINT_CONTENT_AREA = true;
+
+  private static final boolean PAINT_TAB = true;
+
+  private static final boolean TEXT_ICON_GAP_COMPENSATE = true;
+
+  private static final int DEFAULT_SELECTED_INDEX = 3;
+
+  private static final int DEFAULT_TAB_COUNT = 7;
+
+  private static final int EXTRA_SIZE = 2;
+
+  private static final String EMPTY_STRING = "";
+
+  // Order is IMPORTANT!!!
+  private static final Direction[] DIRECTIONS = new Direction[]{Direction.UP, Direction.LEFT, Direction.DOWN, Direction.RIGHT};
+
+  private final Insets[] areaInsets = new Insets[DIRECTIONS.length];
+
+  private final Insets[] normalInsets = new Insets[DIRECTIONS.length];
+
+  private final Insets[] selectedInsets = new Insets[DIRECTIONS.length];
+
+  private final Insets[] adjustedContentInsets = new Insets[DIRECTIONS.length];
+
+  private final Insets[] adjustedContentInsetsTabAreaHidden = new Insets[DIRECTIONS.length];
+
+  private final Insets[] contentInsets = new Insets[DIRECTIONS.length];
+
+  private final Dimension[] minimumSizes = new Dimension[DIRECTIONS.length];
+
+  private final Dimension[] tabMinimumSizes = new Dimension[DIRECTIONS.length];
+
+  private final int[] spacings = new int[DIRECTIONS.length];
+
+  private final int[] raiseds = new int[DIRECTIONS.length];
+
+  private final Insets[] tabInsets = new Insets[DIRECTIONS.length];
+
+  private final Color[] contentTabAreaBorderColors = new Color[DIRECTIONS.length];
+
+  private final boolean[] swapWidthHeights = new boolean[DIRECTIONS.length];
+
+  private boolean tabAreaNotVisibleFix = false;
+
+  private int scrollOffset = 0;
+
+  private int textIconGap;
+
+  private final PaneUIListener listener;
+
+  private static ComponentCache componentCache = new ComponentCache();
+
+  private final PaneHandler paneHandler = new PaneHandler(new PaneHandlerListener() {
+    public void updating() {
+      setEnabled(false);
+      listener.updating();
+    }
+
+    public void updated() {
+      doInit();
+      setEnabled(true);
+      listener.updated();
+    }
+  });
+
+  private Tab hoveredTab;
+
+  private final TabData tabData = new TabData();
+
+  private boolean tabAreaOpaque;
+
+  private boolean contentOpaque;
+
+  private boolean opaque;
+
+  private boolean tabAreaComponentsOpaque;
+
+  private boolean enabled = true;
+
+  public PaneUI(final PaneUIListener listener) {
+    this.listener = listener;
+  }
+
+  public void init() {
+    paneHandler.update();
+  }
+
+  private void doInit() {
+    initPreCommonValues();
+
+    for (int i = 0; i < DIRECTIONS.length; i++) {
+      PanePainter pane = paneHandler.getPainter(DIRECTIONS[i]);
+      initValues(pane, i, DIRECTIONS[i]);
+      reset(pane);
+    }
+
+    initPostCommonValues();
+  }
+
+  public void dispose() {
+    enabled = false;
+    paneHandler.dispose();
+  }
+
+  public void setEnabled(final boolean enabled) {
+    this.enabled = enabled;
+  }
+
+  private void initPreCommonValues() {
+    {
+      // Hack for some look and feels
+      tabAreaNotVisibleFix = UIManager.getLookAndFeel().getClass().getName().indexOf(".WindowsLookAndFeel") > -1;
+    }
+
+    {
+      // Icon text gap
+      textIconGap = UIManager.getInt("TabbedPane.textIconGap");
+      if (textIconGap <= 0)
+        textIconGap = 4;
+    }
+
+    {
+      // Opaque
+      opaque = paneHandler.getPainter(Direction.UP).isOpaque();
+
+      Boolean contentOp = (Boolean) UIManager.get("TabbedPane.contentOpaque");
+      if (contentOp == null)
+        contentOpaque = opaque;
+      else
+        contentOpaque = contentOp.booleanValue();
+
+      tabAreaOpaque = opaque;
+
+      tabAreaComponentsOpaque = false;
+    }
+  }
+
+  private void initPostCommonValues() {
+    {
+      // Scroll offset
+      for (int i = 0; i < DIRECTIONS.length; i++)
+        scrollOffset = Math.max(scrollOffset, Math.max(minimumSizes[i].width, minimumSizes[i].height));
+    }
+  }
+
+  private void initValues(PanePainter pane, int index, Direction direction) {
+    estimateSwappedTabDirection(pane, index, direction);
+
+    reset(pane);
+
+    pane.setSize(1000, 1000);
+
+    boolean upDown = !direction.isHorizontal();
+
+    for (int i = 0; i < DEFAULT_TAB_COUNT; i++) {
+      pane.addTab(EMPTY_STRING, getComponent());
+    }
+
+    pane.setSelectedIndex(DEFAULT_SELECTED_INDEX);
+
+    pane.doValidation();
+
+    {
+      // Tab insets if any from UImanager
+      Insets insets = UIManager.getInsets("TabbedPane.tabInsets");
+      if (insets == null)
+        insets = new Insets(0, 0, 0, 0);
+
+      if (!upDown)
+        tabInsets[index] = new Insets(0, insets.left, 0, insets.right);
+      else
+        tabInsets[index] = InsetsUtil.EMPTY_INSETS;
+    }
+
+    {
+      // Raised
+      Rectangle bounds = pane.getBoundsAt(0);
+      Rectangle bounds2 = pane.getBoundsAt(pane.getSelectedIndex());
+
+      if (direction == Direction.UP)
+        raiseds[index] = Math.max(0, bounds.y - bounds2.y);
+      else if (direction == Direction.LEFT)
+        raiseds[index] = Math.max(0, bounds.x - bounds2.x);
+      else if (direction == Direction.DOWN)
+        raiseds[index] = raiseds[getDirectionIndex(Direction.UP)];
+      else
+        raiseds[index] = raiseds[getDirectionIndex(Direction.LEFT)];
+    }
+
+    {
+      // Spacing
+      Insets normal = getCalculatedInsets(pane, 0, false, direction);
+      Insets selected = getCalculatedInsets(pane, 0, true, direction);
+
+      if (upDown)
+        spacings[index] = normal.left + normal.right - selected.left - selected.right;
+      else
+        spacings[index] = normal.top + normal.bottom - selected.top - selected.bottom;
+    }
+
+    {
+      // Normal insets
+      normalInsets[index] = getCalculatedInsets(pane, 0, false, direction);
+    }
+
+    {
+      // Selected insets
+      Insets insets = getCalculatedInsets(pane, 0, true, direction);
+      int spacing = spacings[index];
+      int spaceFirst = spacing / 2;
+      int spaceAfter = spacing / 2 + spacing % 2;
+
+      if (direction == Direction.UP) {
+        insets.bottom = normalInsets[index].bottom;
+        insets.top = normalInsets[index].top;
+        insets.left += spaceFirst;
+        insets.right += spaceAfter;
+      }
+      else if (direction == Direction.LEFT) {
+        insets.right = normalInsets[index].right;
+        insets.left = normalInsets[index].left;
+        insets.top += spaceFirst;
+        insets.bottom += spaceAfter;
+      }
+      else if (direction == Direction.RIGHT) {
+        insets.right = normalInsets[index].right;
+        insets.left = normalInsets[index].left;
+        insets.top += spaceFirst;
+        insets.bottom += spaceAfter;
+      }
+      else {
+        insets.bottom = normalInsets[index].bottom;
+        insets.top = normalInsets[index].top;
+        insets.left += spaceFirst;
+        insets.right += spaceAfter;
+      }
+
+      selectedInsets[index] = insets;
+    }
+
+    {
+      // Content insets
+      JPanel c = new JPanel();
+      pane.addTab(EMPTY_STRING, c);
+      pane.setSelectedIndex(pane.getTabCount() - 1);
+      pane.doValidation();
+
+      Point l = SwingUtilities.convertPoint(c.getParent(), c.getLocation(), pane);
+
+      Rectangle bounds = pane.getBoundsAt(0);
+      int top = 0;
+      int left = 0;
+      int bottom = 0;
+      int right = 0;
+
+      if (direction == Direction.UP) {
+        top = l.y - bounds.height - bounds.y;
+        left = l.x;
+        bottom = pane.getHeight() - l.y - c.getHeight();
+        right = pane.getWidth() - l.x - c.getWidth();
+      }
+      else if (direction == Direction.DOWN) {
+        top = l.y;
+        left = l.x;
+        bottom = pane.getHeight() - c.getHeight() - l.y - (pane.getHeight() - bounds.y);
+        right = pane.getWidth() - l.x - c.getWidth();
+      }
+      else if (direction == Direction.LEFT) {
+        top = l.y;
+        left = l.x - bounds.width - bounds.x;
+        bottom = pane.getHeight() - l.y - c.getHeight();
+        right = pane.getWidth() - l.x - c.getWidth();
+      }
+      else {
+        top = l.y;
+        left = l.x;
+        bottom = pane.getHeight() - l.y - c.getHeight();
+        right = pane.getWidth() - c.getWidth() - l.x - (pane.getWidth() - bounds.x);
+      }
+
+      contentInsets[index] = new Insets(top, left, bottom, right);
+
+      Insets i = contentInsets[0];
+      Insets i2 = InsetsUtil.rotate(direction.getNextCW(), i);
+      Insets adjustedInsets = InsetsUtil.max(i, i2);
+      adjustedContentInsets[index] = adjustedInsets;
+      adjustedContentInsetsTabAreaHidden[index] = new Insets(
+          direction == Direction.UP ? adjustedInsets.left : adjustedInsets.top,
+                                    direction == Direction.LEFT ?
+                                                                 adjustedInsets.top : adjustedInsets.left, direction == Direction.DOWN ? adjustedInsets.right
+                                                                                                                                       : adjustedInsets.bottom, direction == Direction.RIGHT ?
+                                                                                                                                                                                              adjustedInsets.bottom :
+                                                                                                                                                                                                adjustedInsets.right);
+
+      pane.removeTabAt(pane.getTabCount() - 1);
+      pane.setSelectedIndex(DEFAULT_SELECTED_INDEX);
+
+      pane.doValidation();
+    }
+
+    {
+      // Minimum sizes
+      Rectangle bounds = pane.getBoundsAt(DEFAULT_SELECTED_INDEX);
+      tabMinimumSizes[index] = new Dimension(bounds.width, bounds.height);
+      minimumSizes[index] = new Dimension(bounds.width - tabInsets[index].left - tabInsets[index].right, bounds.height
+          - tabInsets[index].top - tabInsets[index].bottom);
+    }
+
+    calculateAreaInsets(pane, index, direction);
+
+    estimateContentTabAreaBorderColor(pane, index, direction);
+  }
+
+  private void calculateAreaInsets(PanePainter pane, int index, Direction direction) {
+    {
+      // Area insets
+      pane.setSelectedIndex(0);
+      Rectangle selectedBounds = pane.getBoundsAt(0);
+      pane.setSelectedIndex(DEFAULT_SELECTED_INDEX);
+
+      Rectangle normalBounds = pane.getBoundsAt(0);
+      int left = 0;
+      int top = 0;
+      int bottom = 0;
+      int right = 0;
+
+      if (direction == Direction.UP) {
+        left = Math.min(selectedBounds.x, normalBounds.x);
+        top = Math.min(selectedBounds.y, normalBounds.y);
+        bottom = 0;
+        // right = left;
+      }
+      else if (direction == Direction.DOWN) {
+        left = Math.min(selectedBounds.x, normalBounds.x);
+        top = 0;
+        bottom = pane.getHeight() - Math.max(selectedBounds.y + selectedBounds.height,
+            normalBounds.y + normalBounds.height);
+        // right = left;
+      }
+      else if (direction == Direction.LEFT) {
+        top = Math.min(selectedBounds.y, normalBounds.y);
+        left = Math.min(selectedBounds.x, normalBounds.x);
+        right = 0;
+        // bottom = top;
+      }
+      else {
+        top = Math.min(selectedBounds.y, normalBounds.y);
+        left = 0;
+        right = pane.getWidth() - Math.max(selectedBounds.x + selectedBounds.width,
+            normalBounds.x + normalBounds.width);
+        // bottom = top;
+      }
+
+      Dimension size = pane.getSize();
+
+      reset(pane);
+
+      for (int i = 0; i < 4; i++)
+        pane.addTab(EMPTY_STRING, SizeIcon.EMPTY, getComponent());
+
+      pane.setSelectedIndex(-1);
+
+      pane.setSize(pane.getMinimumSize());
+      pane.doValidation();
+
+      if (!direction.isHorizontal()) {
+        int width = pane.getWidth() - 1;
+
+        boolean found = false;
+
+        while (!found) {
+          width++;
+          pane.setSize(width, pane.getHeight());
+
+          pane.doValidation();
+          found = pane.getBoundsAt(0).y == pane.getBoundsAt(3).y;
+        }
+
+        Rectangle endBounds = pane.getBoundsAt(3);
+        right = pane.getWidth() - endBounds.x - endBounds.width - spacings[index];
+      }
+      else {
+        int height = pane.getHeight() - 1;
+
+        boolean found = false;
+
+        while (!found) {
+          height++;
+          pane.setSize(pane.getWidth(), height);
+
+          pane.doValidation();
+          found = pane.getBoundsAt(0).x == pane.getBoundsAt(3).x;
+        }
+
+        Rectangle endBounds = pane.getBoundsAt(3);
+        bottom = pane.getHeight() - endBounds.y - endBounds.height - spacings[index];
+      }
+
+      areaInsets[index] = new Insets(top, left, bottom, right);
+
+      pane.setSize(size);
+
+      pane.doValidation();
+    }
+  }
+
+  private void estimateContentTabAreaBorderColor(PanePainter pane, int index, final Direction direction) {
+    Dimension preSize = pane.getSize();
+
+    reset(pane);
+
+    pane.addTab(EMPTY_STRING, SizeIcon.EMPTY, getComponent());
+
+    pane.setSelectedIndex(-1);
+
+    Dimension size = pane.getMinimumSize();
+
+    if (direction.isHorizontal())
+      pane.setSize(size.width, size.height * 2);
+    else
+      pane.setSize(size.width * 2, size.height);
+
+    pane.doValidation();
+
+    Rectangle tabBounds = pane.getBoundsAt(0);
+
+    BufferedImage img = new BufferedImage(pane.getWidth(), pane.getHeight(), BufferedImage.TYPE_INT_ARGB);
+
+    int x = 0;
+    int y = 0;
+
+    if (direction == Direction.UP) {
+      x = tabBounds.x + (tabBounds.width / 2);
+      y = pane.getHeight() - contentInsets[index].top - contentInsets[index].bottom - 1;
+    }
+    else if (direction == Direction.DOWN) {
+      x = tabBounds.x + (tabBounds.width / 2);
+      y = contentInsets[index].top + contentInsets[index].bottom;
+    }
+    else if (direction == Direction.LEFT) {
+      x = pane.getWidth() - contentInsets[index].left - contentInsets[index].right - 1;
+      y = tabBounds.y + (tabBounds.height / 2);
+    }
+    else {
+      x += contentInsets[index].left + contentInsets[index].right;
+      y = tabBounds.y + (tabBounds.height / 2);
+    }
+
+    final int px = x;
+    final int py = y;
+
+    RGBImageFilter colorFilter = new RGBImageFilter() {
+      public int filterRGB(int x, int y, int rgb) {
+        if (px == x && py == y) {
+          int r = (rgb >> 16) & 0xff;
+          int g = (rgb >> 8) & 0xff;
+          int b = (rgb) & 0xff;
+          int a = (rgb >> 24) & 0xff;
+          contentTabAreaBorderColors[getDirectionIndex(direction.getOpposite())] = new Color(r, g, b, a);
+        }
+
+        return rgb;
+      }
+    };
+
+    FilteredImageSource source = new FilteredImageSource(img.getSource(), colorFilter);
+    pane.paint(img.getGraphics());
+
+    BufferedImage img2 = new BufferedImage(pane.getWidth(), pane.getHeight(), BufferedImage.TYPE_INT_ARGB);
+    img2.getGraphics().drawImage(Toolkit.getDefaultToolkit().createImage(source), 0, 0, null);
+
+    pane.setSize(preSize);
+
+    pane.doValidation();
+  }
+
+  private void estimateSwappedTabDirection(PanePainter pane, int index, final Direction direction) {
+    reset(pane);
+
+    SizeIcon icon = new SizeIcon(80, 80);
+    SizeIcon icon2 = new SizeIcon(160, 80);
+
+    pane.addTab(EMPTY_STRING, icon, getComponent());
+    pane.doValidation();
+
+    Rectangle bounds = pane.getBoundsAt(0);
+    pane.setIconAt(0, icon2);
+    pane.doValidation();
+    Rectangle bounds2 = pane.getBoundsAt(0);
+
+    swapWidthHeights[index] = bounds2.height > 1.5 * bounds.height;
+  }
+
+  public boolean isContentOpaque() {
+    return contentOpaque;
+  }
+
+  public boolean isOpaque() {
+    return opaque;
+  }
+
+  public boolean isTabAreaComponentsOpaque() {
+    return tabAreaComponentsOpaque;
+  }
+
+  public boolean isTabAreaOpaque() {
+    return tabAreaOpaque;
+  }
+
+  public Font getFont() {
+    return paneHandler.getPainter(Direction.UP).getFont();
+  }
+
+  public boolean isSwapWidthHeight(Direction d) {
+    return swapWidthHeights[getDirectionIndex(d)];
+  }
+
+  public Insets getNormalInsets(Direction d) {
+    return normalInsets[getDirectionIndex(d)];
+  }
+
+  public Insets getSelectedInsets(Direction d) {
+    return selectedInsets[getDirectionIndex(d)];
+  }
+
+  public Insets getNormalTabInsets(Direction areaOrientation, Direction tabDirection) {
+    return getRealTabInsets(areaOrientation, tabDirection, getNormalInsets(areaOrientation));
+  }
+
+  public Insets getSelectedTabInsets(Direction areaOrientation, Direction tabDirection) {
+    return getRealTabInsets(areaOrientation, tabDirection, getSelectedInsets(areaOrientation));
+  }
+
+  private Insets getRealTabInsets(Direction areaOrientation, Direction tabDirection, Insets insets) {
+    insets = InsetsUtil.rotate(tabDirection, insets);
+
+    if (swapWidthHeights[getDirectionIndex(areaOrientation)]) {
+      insets = InsetsUtil.rotate(areaOrientation.getNextCCW(), insets);
+    }
+
+    return insets;
+  }
+
+  public Insets getContentInsets(Direction d, boolean tabAreaVisible) {
+    return tabAreaVisible ?
+                           adjustedContentInsets[getDirectionIndex(d)] : adjustedContentInsetsTabAreaHidden[getDirectionIndex(d)];
+  }
+
+  public Insets getTabAreaInsets(Direction d) {
+    return areaInsets[getDirectionIndex(d)];
+  }
+
+  public Dimension getTabExternalMinSize(Direction d) {
+    return minimumSizes[getDirectionIndex(d)];
+  }
+
+  public Insets getTabInsets(Direction d) {
+    return tabInsets[getDirectionIndex(d)];
+  }
+
+  public int getTabSpacing(Direction d) {
+    return spacings[getDirectionIndex(d)];
+  }
+
+  public int getSelectedRaised(Direction d) {
+    return raiseds[getDirectionIndex(d)];
+  }
+
+  public Color getContentTabAreaBorderColor(Direction d) {
+    return contentTabAreaBorderColors[getDirectionIndex(d)];
+  }
+
+  public int getTabSpacing() {
+    return 0;
+  }
+
+  public int getTextIconGap() {
+    return textIconGap;
+  }
+
+  public int getScrollOffset() {
+    return scrollOffset;
+  }
+
+  private int getWidthCompensate(Direction d) {
+    if (swapWidthHeights[getDirectionIndex(d)])
+      return 0;
+
+    return TEXT_ICON_GAP_COMPENSATE ? getTextIconGap() : 0;
+  }
+
+  private int getHeightCompensate(Direction d) {
+    if (!swapWidthHeights[getDirectionIndex(d)])
+      return 0;
+
+    return TEXT_ICON_GAP_COMPENSATE ? getTextIconGap() : 0;
+  }
+
+  private int getDirectionIndex(Direction d) {
+    for (int i = 0; i < DIRECTIONS.length; i++)
+      if (DIRECTIONS[i] == d)
+        return i;
+
+    return 0;
+  }
+
+  private Insets getCalculatedInsets(PanePainter pane, int index, boolean selected, Direction direction) {
+    Rectangle b = pane.getBoundsAt(index);
+    final int sizer = b.height + b.width;
+
+    Icon icon = pane.getIconAt(index);
+
+    pane.setIconAt(index, new SizeIcon(sizer, sizer));
+
+    if (selected)
+      pane.setSelectedIndex(index);
+
+    Rectangle bounds = pane.getBoundsAt(index);
+
+    pane.setIconAt(index, icon);
+    pane.setSelectedIndex(DEFAULT_SELECTED_INDEX);
+
+    int height = bounds.height - sizer - getHeightCompensate(direction);
+    int width = bounds.width - sizer - getWidthCompensate(direction);
+    int top = height / 2;
+    int left = width / 2 + width % 2;
+    int bottom = height / 2 + height % 2;
+    int right = width / 2;
+
+    return new Insets(top, left, bottom, right);
+  }
+
+  public void setHoveredTab(Tab tab) {
+    if (enabled) {
+      if (tab != hoveredTab) {
+        if (hoveredTab != null && hoveredTab.getTabbedPanel() != null)
+          findDraggableComponentBox(hoveredTab).getParent().repaint();
+
+        hoveredTab = tab;
+
+        if (hoveredTab != null && hoveredTab.getTabbedPanel() != null)
+          findDraggableComponentBox(hoveredTab).getParent().repaint();
+      }
+    }
+  }
+
+  public void paintTabArea(TabbedPanel tp, Graphics g, int x, int y, int width, int height) {
+    if (enabled) {
+      if (tp.isTabAreaVisible()) {
+        tabData.initialize(tp);
+
+        PanePainter pane = paneHandler.getPainter(tabData.getAreaOrientation());
+
+        initTabLocations(pane);
+        Insets aInsets = getTabAreaInsets(tabData.getAreaOrientation());
+
+        if (tp.getTabCount() > 0) {
+          // Adjust x, y
+          if (tabData.getAreaOrientation() == Direction.DOWN) {
+            y += tabData.getTabbedPanelHeight() - height;
+          }
+          else if (tabData.getAreaOrientation() == Direction.RIGHT) {
+            x += tabData.getTabbedPanelWidth() - width;
+          }
+
+          width = x < 0 ? width + x : width;
+          height = y < 0 ? height + y : height;
+
+          x = Math.max(0, x);
+          y = Math.max(0, y);
+
+          if (tabData.isHorizontalLayout())
+            pane.setSize(tabData.getTabbedPanelSize().width, getTabbedPanelExtraSize());
+          else
+            pane.setSize(getTabbedPanelExtraSize(), tabData.getTabbedPanelHeight());
+
+          if (PAINT_TAB_AREA && !(pane.getTabCount() == 0 && tabData.getTabCount() > 0)) {
+            Shape originalClip = g.getClip();
+
+            int tx = -x
+            - (tabData.getAreaOrientation() == Direction.RIGHT ?
+                                                                -tabData.getTabbedPanelWidth() + getTabbedPanelExtraSize() : 0);
+            int ty = -y
+            - (tabData.getAreaOrientation() == Direction.DOWN ?
+                                                               -tabData.getTabbedPanelHeight() + getTabbedPanelExtraSize() : 0);
+
+            Rectangle firstVisibleRect = (Rectangle) tabData.getVisibleTabRects().get(0);
+            Rectangle lastVisibleRect = (Rectangle) tabData.getVisibleTabRects().get(tabData.getTabCount() - 1);
+            Tab lastTab = (Tab) tabData.getTabList().get(tabData.getTabCount() - 1);
+
+            if (tabData.isHorizontalLayout()) {
+              int extraWidth = lastTab.getWidth() == lastVisibleRect.width ? 0 : 2 * tabData.getTabbedPanelSize()
+                                                                           .width
+                                                                           - tabData.getTabAreaWidth();
+              pane.setSize(pane.getWidth() + extraWidth, pane.getHeight());
+
+              pane.doValidation();
+
+              // Before tabs
+              g.clipRect(0, 0, aInsets.left + (firstVisibleRect.width > 0 && firstVisibleRect.x == 0 ? 1 : 0), height);
+              pane.paint(g, tx, ty);
+              g.setClip(originalClip);
+
+              // After tabs
+              tx -= extraWidth;
+
+              int clipExtraWidth = extraWidth == 0 ? 1 : 0;
+              g.clipRect(aInsets.left + tabData.getTabAreaWidth() - clipExtraWidth, 0, width - aInsets.left - tabData.getTabAreaWidth()
+                  + clipExtraWidth, height);
+              pane.paint(g, tx, ty);
+              g.setClip(originalClip);
+            }
+            else {
+              int extraHeight = lastTab.getHeight() == lastVisibleRect.height ? 0 : 2 * tabData.getTabbedPanelSize()
+                                                                              .height
+                                                                              - tabData.getTabAreaHeight();
+              pane.setSize(pane.getWidth(), pane.getHeight() + extraHeight);
+
+              pane.doValidation();
+
+              // Before tabs
+              g.clipRect(0, 0, width, aInsets.top + (firstVisibleRect.height > 0 && firstVisibleRect.y == 0 ? 1 : 0));
+              pane.paint(g, tx, ty);
+              g.setClip(originalClip);
+
+              // After tabs
+              ty -= extraHeight;
+
+              int clipExtraHeight = extraHeight == 0 ? 1 : 0;
+              g.clipRect(0, aInsets.top + tabData.getTabAreaHeight() - clipExtraHeight, width, height - aInsets.top
+                  - tabData.getTabAreaHeight() + clipExtraHeight);
+              pane.paint(g, tx, ty);
+              g.setClip(originalClip);
+            }
+          }
+
+          // First and last tab
+          paintTabs(pane, tabData, g, x, y, width, height, true);
+
+          tabData.reset();
+
+          reset(pane);
+        }
+      }
+    }
+  }
+
+  private void paintTabs(PanePainter pane, TabData tabData, Graphics g, int x, int y, int width, int height, boolean first) {
+    if (enabled) {
+      if (PAINT_TAB) {
+        Tab lastTab = (Tab) tabData.getTabList().get(tabData.getTabList().size() - 1);
+        Rectangle lastVisibleRect = (Rectangle) tabData.getVisibleTabRects().get(tabData.getTabCount() - 1);
+
+        // Fix post/pre tabs
+        initPaintableTabLocations(pane);
+
+        Insets aInsets = getTabAreaInsets(tabData.getAreaOrientation());
+
+        Point l = getLocationInTabbedPanel(lastTab, tabData.getTabbedPanel());
+
+        if (tabData.isHorizontalLayout()) {
+          int w = aInsets.left + aInsets.right + Math.max(0, tabData.getTabAreaWidth() - l.x - lastVisibleRect.width) + EXTRA_SIZE;
+
+          for (int i = 0; i < tabData.getTabList().size(); i++)
+            w += ((Tab) tabData.getTabList().get(i)).getWidth();
+
+          pane.setSize(w, getTabbedPanelExtraSize());
+        }
+        else {
+          int h = aInsets.top + aInsets.bottom + Math.max(0,
+              tabData.getTabAreaHeight() - l.y - lastVisibleRect.height) + EXTRA_SIZE;
+
+          for (int i = 0; i < tabData.getTabList().size(); i++)
+            h += ((Tab) tabData.getTabList().get(i)).getHeight();
+
+          pane.setSize(getTabbedPanelExtraSize(), h);
+        }
+
+        pane.doValidation();
+
+        int index = tabData.getPreTab() == null ? 0 : tabData.getTabCount() > 1 ? 1 : 0;
+
+        Shape originalClip = g.getClip();
+
+        int tx = -x - (tabData.getAreaOrientation() == Direction.RIGHT ?
+                                                                        -tabData.getTabbedPanelWidth() + getTabbedPanelExtraSize() : 0);
+        int ty = -y - (tabData.getAreaOrientation() == Direction.DOWN ?
+                                                                       -tabData.getTabbedPanelHeight() + getTabbedPanelExtraSize() : 0);
+
+        Rectangle visibleRect = (Rectangle) tabData.getVisibleTabRects().get(index);
+        Tab tab = (Tab) tabData.getTabList().get(index);
+
+        if (tabData.isHorizontalLayout()) {
+          tx -= (tabData.getPreTab() != null ? tab.getX() - tabData.getPreTab().getX() + visibleRect.x : visibleRect.x);
+          g.clipRect(aInsets.left, 0, tabData.getTabAreaWidth(), height);
+        }
+        else {
+          ty -= (tabData.getPreTab() != null ? tab.getY() - tabData.getPreTab().getY() + visibleRect.y : visibleRect.y);
+          g.clipRect(0, aInsets.top, width, tabData.getTabAreaHeight());
+        }
+
+        applyFocusAndHover(pane, true);
+        pane.paint(g, tx, ty);
+        applyFocusAndHover(pane, false);
+
+        g.setClip(originalClip);
+      }
+    }
+  }
+
+  private int getTabbedPanelExtraSize() {
+    Insets insets = getContentInsets(tabData.getAreaOrientation(), tabData.getTabbedPanel().isTabAreaVisible());
+
+    if (tabData.isHorizontalLayout())
+      return tabData.getTabAreaHeight() + insets.top + insets.bottom + EXTRA_SIZE;
+    else
+      return tabData.getTabAreaWidth() + insets.left + insets.right + EXTRA_SIZE;
+  }
+
+  public void paintContentArea(TabbedPanelContentPanel p, Graphics g, int x, int y, int width, int height) {
+    if (enabled) {
+      if (PAINT_CONTENT_AREA) {
+        tabData.initialize(p.getTabbedPanel());
+        PanePainter pane = paneHandler.getPainter(tabData.getAreaOrientation());
+
+        initTabLocations(pane);
+
+        int tx = 0;
+        int ty = 0;
+
+        if (tabData.getTabbedPanel().hasContentArea()) {
+          Point l = getLocationInTabbedPanel(p, tabData.getTabbedPanel());
+
+          int yComp = 0;
+          int xComp = 0;
+
+          if (/* !tabData.getTabbedPanel().hasContentArea() || */(pane.getTabCount() == 0 && tabData.getTabCount() > 0)) {
+            if (tabData.getAreaOrientation() == Direction.UP) {
+              yComp = tabData.getTabAreaHeight();
+            }
+            else if (tabData.getAreaOrientation() == Direction.DOWN) {
+              yComp = -tabData.getTabAreaHeight();
+            }
+            else if (tabData.getAreaOrientation() == Direction.LEFT) {
+              xComp = tabData.getTabAreaWidth();
+            }
+            else {
+              xComp = -tabData.getTabAreaWidth();
+            }
+          }
+
+          tx = -l.x + (xComp > 0 ? xComp : 0);
+          ty = -l.y + (yComp > 0 ? yComp : 0);
+
+          int extraWidth = 0;
+          int extraHeight = 0;
+
+          if (tabAreaNotVisibleFix && !tabData.getTabbedPanel().isTabAreaVisible()) {
+            extraWidth = !tabData.isHorizontalLayout() ? tabMinimumSizes[getDirectionIndex(
+                tabData.getAreaOrientation())].width
+                - raiseds[getDirectionIndex(tabData.getAreaOrientation())]
+                          + (tabData.getAreaOrientation() == Direction.LEFT ? areaInsets[getDirectionIndex(
+                              Direction.LEFT)].left
+                              : areaInsets[getDirectionIndex(Direction.RIGHT)].right) : 0;
+                          extraHeight = tabData.isHorizontalLayout() ? tabMinimumSizes[getDirectionIndex(
+                              tabData.getAreaOrientation())].height
+                              - raiseds[getDirectionIndex(tabData.getAreaOrientation())]
+                                        + (tabData.getAreaOrientation() == Direction.UP ? areaInsets[getDirectionIndex(
+                                            Direction.UP)].top
+                                            : areaInsets[getDirectionIndex(Direction.DOWN)].bottom) : 0;
+          }
+
+          tx -= tabData.getAreaOrientation() == Direction.LEFT ? extraWidth : 0;
+          ty -= tabData.getAreaOrientation() == Direction.UP ? extraHeight : 0;
+
+          pane.setSize(tabData.getTabbedPanelSize().width - Math.abs(xComp) + extraWidth, tabData.getTabbedPanelSize()
+              .height
+              - Math.abs(yComp) + extraHeight);
+
+          pane.doValidation();
+        }
+        else {
+          if (tabData.isHorizontalLayout()) {
+            pane.setSize(p.getWidth(), p.getHeight() + tabData.getTabAreaHeight());
+          }
+          else {
+            pane.setSize(p.getWidth() + tabData.getTabAreaWidth(), p.getHeight());
+          }
+
+          pane.doValidation();
+
+          if (tabData.getAreaOrientation() == Direction.UP)
+            ty -= tabData.getTabAreaHeight();
+          else if (tabData.getAreaOrientation() == Direction.LEFT)
+            tx -= tabData.getTabAreaWidth();
+        }
+
+        pane.paint(g, tx, ty);
+
+        tabData.reset();
+
+        reset(pane);
+      }
+    }
+  }
+
+  private Component getComponent() {
+    return componentCache.getComponent();
+  }
+
+  private void reset(PanePainter pane) {
+    pane.removeAllTabs();
+    componentCache.reset();
+  }
+
+  private Point getLocationInTabbedPanel(Component c, TabbedPanel tp) {
+    Point l = SwingUtilities.convertPoint(c.getParent(), c.getLocation(), tp);
+    Insets tpInsets = tp.getInsets();
+    l.x -= tpInsets.left;
+    l.y -= tpInsets.top;
+
+    return l;
+  }
+
+  private void initPaintableTabLocations(PanePainter pane) {
+    reset(pane);
+
+    if (tabData.getPreTab() != null) {
+      tabData.getTabList().add(0, tabData.getPreTab());
+      tabData.getVisibleTabRects().add(0, new Rectangle(0, 0, 0, 0));
+    }
+
+    if (tabData.getPostTab() != null) {
+      tabData.getTabList().add(tabData.getPostTab());
+      tabData.getVisibleTabRects().add(new Rectangle(0, 0, 0, 0));
+    }
+
+    int size = 0;
+    int selectedIndex = -1;
+
+    for (int i = 0; i < tabData.getTabCount(); i++) {
+      final Tab tab = (Tab) tabData.getTabList().get(i);
+
+      SizeIcon icon = new SizeIcon(getInternalTabWidth(tab) - getWidthCompensate(tabData.getAreaOrientation()), getInternalTabHeight(
+          tab)
+          - getHeightCompensate(
+              tabData.getAreaOrientation()), isSwapWidthHeight(
+                  tabData.getAreaOrientation()));
+
+      pane.addTab(EMPTY_STRING, icon, getComponent());
+
+      if (tab.isHighlighted())
+        selectedIndex = pane.getTabCount() - 1;
+
+      if (!tab.isEnabled()) {
+        pane.setEnabledAt(i, false);
+        pane.setDisabledIconAt(i, icon);
+      }
+
+      size += tabData.isHorizontalLayout() ? tab.getWidth() : tab.getHeight();
+    }
+
+    pane.setSelectedIndex(selectedIndex);
+    pane.doValidation();
+  }
+
+  private void applyFocusAndHover(PanePainter pane, boolean active) {
+    if (active) {
+      for (int i = 0; i < tabData.getTabCount(); i++) {
+        Tab tab = (Tab) tabData.getTabList().get(i);
+
+        if (tab.getFocusableComponent() != null && tab.getFocusableComponent().hasFocus()) {
+          pane.setMouseEntered(true);
+          pane.setFocusActive(true);
+          break;
+        }
+      }
+
+      if (hoveredTab != null) {
+        for (int i = 0; i < tabData.getTabCount(); i++) {
+          Tab tab = (Tab) tabData.getTabList().get(i);
+
+          if (tab == hoveredTab) {
+            pane.setMouseEntered(true);
+            pane.setHoveredTab(i);
+            break;
+          }
+        }
+      }
+    }
+    else {
+      pane.setFocusActive(false);
+      pane.setMouseEntered(false);
+    }
+  }
+
+  private int getInternalTabWidth(Tab tab) {
+    Direction areaOrientation = tab.getTabbedPanel().getProperties().getTabAreaOrientation();
+    Insets insets = tab.isHighlighted() ? getSelectedInsets(areaOrientation) : getNormalInsets(areaOrientation);
+    int width = tab.getWidth();
+
+    width -= insets.left + insets.right;
+
+    if (areaOrientation == Direction.LEFT || areaOrientation == Direction.RIGHT)
+      width -= getSelectedRaised(areaOrientation);
+
+    return width;
+  }
+
+  private int getInternalTabHeight(Tab tab) {
+    Direction areaOrientation = tab.getTabbedPanel().getProperties().getTabAreaOrientation();
+    Insets insets = tab.isHighlighted() ? getSelectedInsets(areaOrientation) : getNormalInsets(areaOrientation);
+    int height = tab.getHeight();
+
+    height -= insets.top + insets.bottom;
+
+    if (areaOrientation == Direction.UP || areaOrientation == Direction.DOWN)
+      height -= getSelectedRaised(areaOrientation);
+
+    return height;
+  }
+
+  private void initTabLocations(PanePainter pane) {
+    findPaintableTabs();
+
+    Dimension minSize = getTabExternalMinSize(tabData.getAreaOrientation());
+    Insets aInsets = getTabAreaInsets(tabData.getAreaOrientation());
+
+    int selectedIndex = -1;
+
+    if (tabData.getTabbedPanel().isTabAreaVisible()) {
+      for (int i = 0; i < tabData.getTabCount(); i++) {
+        final Tab tab = (Tab) tabData.getTabList().get(i);
+        final Rectangle visibleRect = (Rectangle) tabData.getVisibleTabRects().get(i);
+        Insets insets = getTabInsets(tabData.getAreaOrientation());
+
+        int iconWidth = Math.max(-insets.left - insets.right,
+            getInternalTabWidth(tab) - (tab.getWidth() - visibleRect.width));
+
+        int iconHeight = Math.max(-insets.top - insets.bottom,
+            getInternalTabHeight(tab) - (tab.getHeight() - visibleRect.height));
+
+        Point l = getLocationInTabbedPanel(tab, tabData.getTabbedPanel());
+
+        if ((tabData.isHorizontalLayout() && (visibleRect.width >= minSize.width || minSize.width < tabData.getTabbedPanelWidth() - l.x
+            - aInsets.right))
+            || (!tabData.isHorizontalLayout() && (visibleRect.height >= minSize.height || minSize.height < tabData.getTabbedPanelHeight()
+                - l.y - aInsets.bottom))) {
+          final int iWidth = iconWidth;
+          final int iHeight = iconHeight;
+
+          SizeIcon icon = new SizeIcon(iWidth - getWidthCompensate(tabData.getAreaOrientation()), iHeight
+              - getHeightCompensate(
+                  tabData.getAreaOrientation()), isSwapWidthHeight(
+                      tabData.getAreaOrientation()));
+
+          int j = pane.getTabCount();
+          pane.addTab(EMPTY_STRING, icon, getComponent());
+
+          if (i == tabData.getSelectedTabPainterIndex()) {
+            selectedIndex = j;
+          }
+
+          if (!tab.isEnabled()) {
+            pane.setEnabledAt(j, false);
+            pane.setDisabledIconAt(j, icon);
+          }
+        }
+      }
+    }
+    else if (tabAreaNotVisibleFix) {
+      pane.addTab(EMPTY_STRING, componentCache.getComponent());
+    }
+
+    if (pane.getTabCount() > 0)
+      pane.setSelectedIndex(selectedIndex);
+
+    pane.doValidation();
+  }
+
+  private void findPaintableTabs() {
+    Tab firstTab = null;
+    Rectangle firstVisibleRect = null;
+    Tab previousTab = null;
+
+    int i = 0;
+    boolean tabsFound = false;
+
+    if (tabData.getTabbedPanel().isTabAreaVisible()) {
+      while (i < tabData.getTabbedPanel().getTabCount()) {
+        Tab tab = tabData.getTabbedPanel().getTabAt(i);
+        Rectangle r = tab.getVisibleRect();
+
+        if (i == 0) {
+          firstTab = tab;
+          firstVisibleRect = r;
+        }
+
+        i++;
+
+        if (r.width > 0 && r.height > 0) {
+          tabsFound = true;
+          tabData.getTabList().add(tab);
+          tabData.getVisibleTabRects().add(r);
+
+          if (tabData.getTabCount() == 1)
+            tabData.setPreTab(previousTab);
+
+          if (tab.isHighlighted()) {
+            tabData.setSelectedTabPainterIndex(tabData.getTabCount() - 1);
+          }
+        }
+        else if (tabData.getTabList().size() > 0 && (r.width == 0 || r.height == 0))
+          tabData.setPostTab(tab);
+
+        if (tabsFound
+            && r.x == 0
+            && r.y == 0
+            && ((tabData.isHorizontalLayout() && r.width < tab.getWidth()) || (!tabData.isHorizontalLayout() && r.height < tab.getHeight()))) {
+          break;
+        }
+
+        previousTab = tab;
+      }
+
+      if (firstTab != null) {
+        // TODO: Ugly!
+        Component box = findDraggableComponentBox(firstTab);
+
+        if (box != null) {
+          if (tabData.isHorizontalLayout()) {
+            tabData.setTabAreaWidth(box.getWidth());
+            tabData.setTabAreaHeight(box.getParent().getHeight());
+          }
+          else {
+            tabData.setTabAreaWidth(box.getParent().getWidth());
+            tabData.setTabAreaHeight(box.getHeight());
+          }
+        }
+
+        if (tabData.getTabCount() == 0) {
+          tabData.getTabList().add(firstTab);
+          tabData.getVisibleTabRects().add(firstVisibleRect);
+        }
+      }
+    }
+  }
+
+  private Component findDraggableComponentBox(Component c) {
+    if (c == null || c instanceof DraggableComponentBox)
+      return c;
+
+    return findDraggableComponentBox(c.getParent());
+  }
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneUIListener.java b/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneUIListener.java
new file mode 100644
index 0000000..6f9260c
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/internal/laftheme/PaneUIListener.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: PaneUIListener.java,v 1.2 2005/12/04 12:56:44 jesper Exp $
+package net.infonode.tabbedpanel.theme.internal.laftheme;
+
+public interface PaneUIListener {
+  void updating();
+
+  void updated();
+}
diff --git a/src/net/infonode/tabbedpanel/theme/internal/laftheme/SizeIcon.java b/src/net/infonode/tabbedpanel/theme/internal/laftheme/SizeIcon.java
new file mode 100644
index 0000000..f04898d
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/internal/laftheme/SizeIcon.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SizeIcon.java,v 1.3 2005/12/04 13:46:05 jesper Exp $
+package net.infonode.tabbedpanel.theme.internal.laftheme;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class SizeIcon implements Icon {
+  public static SizeIcon EMPTY = new SizeIcon(0, 0);
+
+  private int width;
+  private int height;
+  private boolean swap;
+
+  public SizeIcon(int width, int height) {
+    this(width, height, false);
+  }
+
+  public SizeIcon(int width, int height, boolean swap) {
+    this.width = width;
+    this.height = height;
+    this.swap = swap;
+  }
+
+  public void paintIcon(Component c, Graphics g, int x, int y) {
+  }
+
+  public int getIconWidth() {
+    return swap ? height : width;
+  }
+
+  public int getIconHeight() {
+    return swap ? width : height;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/theme/internal/laftheme/TabData.java b/src/net/infonode/tabbedpanel/theme/internal/laftheme/TabData.java
new file mode 100644
index 0000000..eb89765
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/internal/laftheme/TabData.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TabData.java,v 1.4 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.tabbedpanel.theme.internal.laftheme;
+
+import java.awt.Dimension;
+import java.util.ArrayList;
+
+import net.infonode.gui.DimensionUtil;
+import net.infonode.tabbedpanel.Tab;
+import net.infonode.tabbedpanel.TabbedPanel;
+import net.infonode.util.Direction;
+
+class TabData {
+  private final ArrayList tabList = new ArrayList();
+
+  private final ArrayList visibleTabRects = new ArrayList();
+
+  private TabbedPanel tabbedPanel;
+
+  private Direction areaOrientation;
+
+  private int tabAreaHeight;
+
+  private int tabAreaWidth;
+
+  private int selectedTabPainterIndex;
+
+  private Dimension tpInternalSize;
+
+  private Tab preTab;
+  private Tab postTab;
+
+  public TabData() {
+    reset();
+  }
+
+  public void reset() {
+    tabList.clear();
+    visibleTabRects.clear();
+    tabbedPanel = null;
+    areaOrientation = null;
+    tabAreaHeight = 0;
+    tabAreaWidth = 0;
+    selectedTabPainterIndex = -1;
+    tpInternalSize = null;
+    preTab = null;
+    postTab = null;
+  }
+
+  public ArrayList getTabList() {
+    return tabList;
+  }
+
+  public ArrayList getVisibleTabRects() {
+    return visibleTabRects;
+  }
+
+  public Direction getAreaOrientation() {
+    return areaOrientation;
+  }
+
+  public TabbedPanel getTabbedPanel() {
+    return tabbedPanel;
+  }
+
+  public void initialize(TabbedPanel tabbedPanel) {
+    this.tabbedPanel = tabbedPanel;
+    areaOrientation = tabbedPanel.getProperties().getTabAreaOrientation();
+    tpInternalSize = DimensionUtil.getInnerDimension(tabbedPanel.getSize(), tabbedPanel.getInsets());
+  }
+
+  public Dimension getTabbedPanelSize() {
+    return tpInternalSize;
+  }
+
+  public int getTabbedPanelWidth() {
+    return tpInternalSize.width;
+  }
+
+  public int getTabbedPanelHeight() {
+    return tpInternalSize.height;
+  }
+
+  public boolean isHorizontalLayout() {
+    return !areaOrientation.isHorizontal();
+  }
+
+  public int getSelectedTabPainterIndex() {
+    return selectedTabPainterIndex;
+  }
+
+  public void setSelectedTabPainterIndex(int selectedTabPainterIndex) {
+    this.selectedTabPainterIndex = selectedTabPainterIndex;
+  }
+
+  public int getTabCount() {
+    return tabList.size();
+  }
+
+  public int getTabAreaHeight() {
+    return tabAreaHeight;
+  }
+
+  public void setTabAreaHeight(int tabAreaHeight) {
+    this.tabAreaHeight = tabAreaHeight;
+  }
+
+  public int getTabAreaWidth() {
+    return tabAreaWidth;
+  }
+
+  public void setTabAreaWidth(int tabAreaWidth) {
+    this.tabAreaWidth = tabAreaWidth;
+  }
+
+  public Tab getPostTab() {
+    return postTab;
+  }
+
+  public void setPostTab(Tab postTab) {
+    this.postTab = postTab;
+  }
+
+  public Tab getPreTab() {
+    return preTab;
+  }
+
+  public void setPreTab(Tab preTab) {
+    this.preTab = preTab;
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_down_hovered.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_down_hovered.png
new file mode 100644
index 0000000..3a1100b
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_down_hovered.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_down_normal.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_down_normal.png
new file mode 100644
index 0000000..ba8012e
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_down_normal.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_down_pressed.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_down_pressed.png
new file mode 100644
index 0000000..1eb3172
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_down_pressed.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_dropdown_hovered.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_dropdown_hovered.png
new file mode 100644
index 0000000..f8616e7
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_dropdown_hovered.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_dropdown_normal.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_dropdown_normal.png
new file mode 100644
index 0000000..96aae4c
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_dropdown_normal.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_dropdown_pressed.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_dropdown_pressed.png
new file mode 100644
index 0000000..5bdd231
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_dropdown_pressed.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_left_hovered.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_left_hovered.png
new file mode 100644
index 0000000..29d9845
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_left_hovered.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_left_normal.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_left_normal.png
new file mode 100644
index 0000000..b012a61
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_left_normal.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_left_pressed.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_left_pressed.png
new file mode 100644
index 0000000..40cf902
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_left_pressed.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_right_hovered.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_right_hovered.png
new file mode 100644
index 0000000..0734b94
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_right_hovered.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_right_normal.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_right_normal.png
new file mode 100644
index 0000000..5519bac
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_right_normal.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_right_pressed.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_right_pressed.png
new file mode 100644
index 0000000..c22b1b4
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_right_pressed.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_up_hovered.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_up_hovered.png
new file mode 100644
index 0000000..be800d9
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_up_hovered.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_up_normal.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_up_normal.png
new file mode 100644
index 0000000..195fda0
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_up_normal.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_up_pressed.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_up_pressed.png
new file mode 100644
index 0000000..0887e7d
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/button_up_pressed.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_down.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_down.png
new file mode 100644
index 0000000..1790c36
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_down.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_left.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_left.png
new file mode 100644
index 0000000..2686501
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_left.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_right.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_right.png
new file mode 100644
index 0000000..07dc151
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_right.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_up.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_up.png
new file mode 100644
index 0000000..1d9b1b3
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_hovered_up.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_down.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_down.png
new file mode 100644
index 0000000..6cdcd64
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_down.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_left.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_left.png
new file mode 100644
index 0000000..f34898b
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_left.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_right.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_right.png
new file mode 100644
index 0000000..b38805d
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_right.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_up.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_up.png
new file mode 100644
index 0000000..14b3ed1
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_normal_up.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_down.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_down.png
new file mode 100644
index 0000000..47a2a07
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_down.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_left.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_left.png
new file mode 100644
index 0000000..832211b
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_left.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_right.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_right.png
new file mode 100644
index 0000000..fff73d6
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_right.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_up.png b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_up.png
new file mode 100644
index 0000000..e8201ef
Binary files /dev/null and b/src/net/infonode/tabbedpanel/theme/internal/resource/xp/tab_selected_up.png differ
diff --git a/src/net/infonode/tabbedpanel/theme/package.html b/src/net/infonode/tabbedpanel/theme/package.html
new file mode 100644
index 0000000..9c3fa4a
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/theme/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Themes with different looks for both TabbedPanel and TitledTab
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/titledtab/TitledTab.java b/src/net/infonode/tabbedpanel/titledtab/TitledTab.java
new file mode 100644
index 0000000..71ed076
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/titledtab/TitledTab.java
@@ -0,0 +1,1157 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitledTab.java,v 1.89 2009/02/05 15:57:56 jesper Exp $
+package net.infonode.tabbedpanel.titledtab;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.Icon;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.border.CompoundBorder;
+import javax.swing.border.EmptyBorder;
+import javax.swing.plaf.PanelUI;
+
+import net.infonode.gui.DimensionProvider;
+import net.infonode.gui.InsetsUtil;
+import net.infonode.gui.RotatableLabel;
+import net.infonode.gui.TranslatingShape;
+import net.infonode.gui.border.FocusBorder;
+import net.infonode.gui.hover.HoverEvent;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.gui.hover.hoverable.HoverManager;
+import net.infonode.gui.hover.hoverable.Hoverable;
+import net.infonode.gui.icon.IconProvider;
+import net.infonode.gui.layout.StackableLayout;
+import net.infonode.gui.panel.SimplePanel;
+import net.infonode.gui.shaped.panel.ShapedPanel;
+import net.infonode.properties.base.Property;
+import net.infonode.properties.gui.InternalPropertiesUtil;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.PropertyMapTreeListener;
+import net.infonode.properties.propertymap.PropertyMapWeakListenerManager;
+import net.infonode.properties.util.PropertyChangeListener;
+import net.infonode.tabbedpanel.*;
+import net.infonode.util.Alignment;
+import net.infonode.util.Direction;
+import net.infonode.util.ValueChange;
+
+/**
+ * <p>A TitledTab is a tab that has support for text, icon and a custom Swing component
+ * (called title component). Titled tab supports several properties that makes it possible
+ * to change the look (borders, colors, insets), layout (up, down, left, right).</p>
+ *
+ * <p>Titled tab has a line based layout, i.e. the text, icon and title component are
+ * laid out in a line. The layout of the tab can be rotated, i.e. the text and the icon will
+ * be rotated 90, 180 or 270 degrees. The title component will not be rotated but moved so
+ * that the line layout will persist.</p>
+ *
+ * <p>A titled tab has 3 rendering states:
+ * <ul>
+ * <li>Normal - The tab is selectable but not yet selected
+ * <li>Highlighted - The tab is either highlighted or selected
+ * <li>Disabled - The tab is disabled and cannot be selected or highlighted
+ * </ul>Most of the properties for the tab can be configured for each of the tab rendering
+ * states.</p>
+ *
+ * <p><strong>Note:</strong> If only the normal state properties have been configured, the
+ * highlighted and disabled state will automatically use the same properties as for the normal
+ * state, see {@link TitledTabProperties} and {@link TitledTabStateProperties}.</p>
+ *
+ * <p>TitledTab implements the {@link net.infonode.gui.icon.IconProvider} interface and
+ * overloads toString() so that both text and icon for the normal state is shown in the
+ * tab drop down list in a tabbed panel.</p>
+ *
+ * <p>TitledTab supports mouse hovering. A {@link HoverListener} can be set in the
+ * {@link TitledTabProperties}. The hover listener receives a {@link HoverEvent} when the mouse
+ * enters or exits the tab. The hover event's source will be the affected titled tab.</p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.89 $
+ * @see TitledTabProperties
+ * @see TitledTabStateProperties
+ */
+public class TitledTab extends Tab implements IconProvider {
+  private static PanelUI UI = new PanelUI() {
+  };
+
+  private class StatePanel extends SimplePanel {
+    private final ShapedPanel panel = new ShapedPanel();
+    private final SimplePanel titleComponentPanel = new SimplePanel();
+    private final RotatableLabel label = new RotatableLabel(null, null) {
+      public Dimension getPreferredSize() {
+        Dimension d = super.getPreferredSize();
+        String text = this.getText();
+        Icon tmpIcon = this.getIcon();
+
+        if (text == null || tmpIcon == null) {
+          this.setText(" ");
+          this.setIcon(icon);
+          if (getDirection().isHorizontal())
+            d = new Dimension(d.width, super.getPreferredSize().height);
+          else
+            d = new Dimension(super.getPreferredSize().width, d.height);
+
+          this.setText(text);
+          this.setIcon(tmpIcon);
+        }
+
+        return d;
+      }
+    };
+    private JComponent titleComponent;
+    private Direction currentLayoutDirection;
+    private int currentLayoutGap = -1;
+    private Alignment currentLayoutAlignment;
+    private String toolTipText;
+    private Icon icon;
+
+    public StatePanel(Border focusBorder) {
+      super(new BorderLayout());
+
+      label.setBorder(focusBorder);
+      label.setMinimumSize(new Dimension(0, 0));
+
+      panel.add(label, BorderLayout.CENTER);
+      add(panel, BorderLayout.CENTER);
+    }
+
+    public String getToolTipText() {
+      return toolTipText;
+    }
+
+    public JComponent getTitleComponent() {
+      return titleComponent;
+    }
+
+    public Shape getShape() {
+      return panel.getShape();
+    }
+
+    public JLabel getLabel() {
+      return label;
+    }
+
+    public void setTitleComponent(JComponent titleComponent, TitledTabStateProperties stateProps) {
+      JComponent oldTitleComponent = this.titleComponent;
+      this.titleComponent = null;
+      if (oldTitleComponent != null && oldTitleComponent.getParent() == titleComponentPanel)
+        titleComponentPanel.remove(oldTitleComponent);
+      this.titleComponent = titleComponent;
+      updateLayout(stateProps, true);
+    }
+
+    public void activateTitleComponent() {
+      if (titleComponent != null) {
+        if (titleComponent.getParent() != titleComponentPanel) {
+          if (titleComponent.getParent() != null)
+            titleComponent.getParent().remove(titleComponent);
+          titleComponentPanel.add(titleComponent, BorderLayout.CENTER);
+        }
+      }
+      else {
+        titleComponentPanel.removeAll();
+      }
+    }
+
+    public void activate() {
+      remove(panel);
+      eventPanel.add(panel, BorderLayout.CENTER);
+      add(eventPanel, BorderLayout.CENTER);
+    }
+
+    public void deactivate() {
+      remove(eventPanel);
+      eventPanel.remove(panel);
+      add(panel, BorderLayout.CENTER);
+    }
+
+    public Dimension getPreferredSize() {
+      activateTitleComponent();
+
+      return getAdjustedSize(super.getPreferredSize());
+    }
+
+    public Dimension getMinimumSize() {
+      activateTitleComponent();
+
+      return getAdjustedSize(super.getMinimumSize());
+    }
+
+    public Dimension getMaximumSize() {
+      activateTitleComponent();
+      return super.getMaximumSize();
+    }
+
+    private Dimension getAdjustedSize(Dimension d) {
+      DimensionProvider prov = properties.getMinimumSizeProvider();
+      if (prov == null)
+        return d;
+
+      Dimension min = properties.getMinimumSizeProvider().getDimension(this);
+
+      if (min == null)
+        return d;
+
+      return new Dimension(Math.max(min.width, d.width), Math.max(min.height, d.height));
+    }
+
+    public JComponent getFocusableComponent() {
+      return label;
+    }
+
+    private void updateLayout(TitledTabStateProperties stateProperties, boolean titleComponentChanged) {
+      if (titleComponent != null && stateProperties.getTitleComponentVisible()) {
+        Direction d = stateProperties.getDirection();
+        int gap;
+        if (stateProperties.getIconVisible() || stateProperties.getTextVisible())
+          gap = stateProperties.getTextTitleComponentGap();
+        else
+          gap = 0;
+        Alignment alignment = stateProperties.getTitleComponentTextRelativeAlignment();
+        if (titleComponentPanel.getComponentCount() == 0 ||
+            (titleComponentPanel.getComponentCount() > 0 && titleComponentPanel.getComponent(0) != titleComponent) ||
+            titleComponentChanged ||
+            gap != currentLayoutGap ||
+            alignment != currentLayoutAlignment ||
+            d != currentLayoutDirection) {
+          titleComponentChanged = false;
+          currentLayoutDirection = d;
+          currentLayoutGap = gap;
+          currentLayoutAlignment = alignment;
+
+          panel.remove(titleComponentPanel);
+          if (d == Direction.UP) {
+            panel.add(titleComponentPanel, alignment == Alignment.LEFT ? BorderLayout.SOUTH : BorderLayout.NORTH);
+            titleComponentPanel.setBorder(
+                new EmptyBorder(alignment == Alignment.LEFT ? gap : 0, 0, alignment == Alignment.LEFT ? 0 : gap, 0));
+          }
+          else if (d == Direction.LEFT) {
+            panel.add(titleComponentPanel, alignment == Alignment.LEFT ? BorderLayout.EAST : BorderLayout.WEST);
+            titleComponentPanel.setBorder(
+                new EmptyBorder(0, alignment == Alignment.LEFT ? gap : 0, 0, alignment == Alignment.LEFT ? 0 : gap));
+          }
+          else if (d == Direction.DOWN) {
+            panel.add(titleComponentPanel, alignment == Alignment.LEFT ? BorderLayout.NORTH : BorderLayout.SOUTH);
+            titleComponentPanel.setBorder(
+                new EmptyBorder(alignment == Alignment.LEFT ? 0 : gap, 0, alignment == Alignment.LEFT ? gap : 0, 0));
+          }
+          else {
+            panel.add(titleComponentPanel, alignment == Alignment.LEFT ? BorderLayout.WEST : BorderLayout.EAST);
+            titleComponentPanel.setBorder(
+                new EmptyBorder(0, alignment == Alignment.LEFT ? 0 : gap, 0, alignment == Alignment.LEFT ? gap : 0));
+          }
+
+          panel.revalidate();
+        }
+      }
+      else {
+        panel.remove(titleComponentPanel);
+        titleComponentPanel.removeAll();
+
+        panel.revalidate();
+      }
+    }
+
+    public void updateShapedPanel(TitledTabStateProperties stateProperties) {
+      Direction tabAreaOrientation = getTabAreaOrientation();
+      ShapedPanelProperties shapedPanelProperties = stateProperties.getShapedPanelProperties();
+      InternalPropertiesUtil.applyTo(shapedPanelProperties, panel, tabAreaOrientation.getNextCW());
+      panel
+      .setHorizontalFlip(tabAreaOrientation == Direction.DOWN || tabAreaOrientation == Direction.LEFT ? !shapedPanelProperties
+                                                                                                      .getHorizontalFlip()
+                                                                                                      : shapedPanelProperties.getHorizontalFlip());
+    }
+
+    public void setBorders(Border outerBorder, Border innerBorder) {
+      setBorder(outerBorder);
+      panel.setBorder(innerBorder);
+    }
+
+    public boolean updateState(Map changes, TitledTabStateProperties stateProperties) {
+      boolean updateBorders = false;
+
+      if (changes == null) {
+        label.setText(stateProperties.getTextVisible() ? stateProperties.getText() : null);
+
+        icon = stateProperties.getIcon();
+        label.setIcon(stateProperties.getIconVisible() ? stateProperties.getIcon() : null);
+
+        label.setIconTextGap(stateProperties.getIconTextGap());
+
+        label.setDirection(stateProperties.getDirection());
+
+        Alignment alignment = stateProperties.getIconTextRelativeAlignment();
+        label.setHorizontalTextPosition(alignment == Alignment.LEFT ? JLabel.RIGHT :
+          JLabel.LEFT);
+
+        alignment = stateProperties.getHorizontalAlignment();
+        label.setHorizontalAlignment(alignment == Alignment.LEFT ? JLabel.LEFT :
+          alignment == Alignment.CENTER ? JLabel.CENTER :
+            JLabel.RIGHT);
+
+        alignment = stateProperties.getVerticalAlignment();
+        label.setVerticalAlignment(alignment == Alignment.TOP ? JLabel.TOP :
+          alignment == Alignment.CENTER ? JLabel.CENTER :
+            JLabel.BOTTOM);
+
+        toolTipText = stateProperties.getToolTipEnabled() ? stateProperties.getToolTipText() : null;
+        if (toolTipText != null && toolTipText.length() == 0)
+          toolTipText = null;
+        if (currentStatePanel == this)
+          eventPanel.setToolTipText(toolTipText);
+
+        updateLayout(stateProperties, true);
+
+        ComponentProperties componentProperties = stateProperties.getComponentProperties();
+        label.setFont(componentProperties.getFont());
+
+        Color c = componentProperties.getForegroundColor();
+        label.setForeground(c);
+        setForeground(c);
+
+        updateShapedPanel(stateProperties);
+
+        updateBorders = true;
+      }
+      else {
+        Map m = (Map) changes.get(stateProperties.getMap());
+        if (m != null) {
+          Set keySet = m.keySet();
+
+          if (keySet.contains(TitledTabStateProperties.TEXT) || keySet.contains(TitledTabStateProperties.TEXT_VISIBLE)) {
+            label.setText(stateProperties.getTextVisible() ? stateProperties.getText() : null);
+          }
+
+          if (keySet.contains(TitledTabStateProperties.ICON) || keySet.contains(TitledTabStateProperties.ICON_VISIBLE)) {
+            icon = stateProperties.getIcon();
+            label.setIcon(stateProperties.getIconVisible() ? stateProperties.getIcon() : null);
+          }
+
+          if (keySet.contains(TitledTabStateProperties.ICON_TEXT_GAP)) {
+            label.setIconTextGap(
+                ((Integer) ((ValueChange) m.get(TitledTabStateProperties.ICON_TEXT_GAP)).getNewValue()).intValue());
+          }
+
+          if (keySet.contains(TitledTabStateProperties.ICON_TEXT_RELATIVE_ALIGNMENT)) {
+            Alignment alignment = (Alignment) ((ValueChange) m.get(
+                TitledTabStateProperties.ICON_TEXT_RELATIVE_ALIGNMENT)).getNewValue();
+            label.setHorizontalTextPosition(alignment == Alignment.LEFT ? JLabel.RIGHT : JLabel.LEFT);
+          }
+
+          if (keySet.contains(TitledTabStateProperties.HORIZONTAL_ALIGNMENT)) {
+            Alignment alignment = (Alignment) ((ValueChange) m.get(TitledTabStateProperties.HORIZONTAL_ALIGNMENT)).getNewValue();
+            label.setHorizontalAlignment(
+                alignment == Alignment.LEFT ?
+                                             JLabel.LEFT : alignment == Alignment.CENTER ? JLabel.CENTER : JLabel.RIGHT);
+          }
+
+          if (keySet.contains(TitledTabStateProperties.VERTICAL_ALIGNMENT)) {
+            Alignment alignment = (Alignment) ((ValueChange) m.get(TitledTabStateProperties.VERTICAL_ALIGNMENT)).getNewValue();
+            label.setVerticalAlignment(
+                alignment == Alignment.TOP ?
+                                            JLabel.TOP : alignment == Alignment.CENTER ? JLabel.CENTER : JLabel.BOTTOM);
+          }
+
+          if (keySet.contains(TitledTabStateProperties.TOOL_TIP_TEXT) || keySet.contains(
+              TitledTabStateProperties.TOOL_TIP_ENABLED)) {
+            toolTipText = stateProperties.getToolTipEnabled() ? stateProperties.getToolTipText() : null;
+            if (toolTipText != null && toolTipText.length() == 0)
+              toolTipText = null;
+
+            if (currentStatePanel == this)
+              eventPanel.setToolTipText(toolTipText);
+          }
+
+          if (keySet.contains(TitledTabStateProperties.DIRECTION) || keySet.contains(
+              TitledTabStateProperties.TITLE_COMPONENT_TEXT_RELATIVE_ALIGNMENT)
+              || keySet.contains(TitledTabStateProperties.TITLE_COMPONENT_VISIBLE) || keySet.contains(
+                  TitledTabStateProperties.TEXT_TITLE_COMPONENT_GAP)
+                  || keySet.contains(TitledTabStateProperties.ICON_VISIBLE) || keySet.contains(
+                      TitledTabStateProperties.TEXT_VISIBLE)) {
+            label.setDirection(stateProperties.getDirection());
+
+            updateLayout(stateProperties, keySet.contains(TitledTabStateProperties.TITLE_COMPONENT_VISIBLE));
+          }
+
+          if (keySet.contains(TitledTabStateProperties.DIRECTION)) {
+            updateBorders = true;
+          }
+        }
+
+        m = (Map) changes.get(stateProperties.getComponentProperties().getMap());
+        if (m != null) {
+          Set keySet = m.keySet();
+
+          if (keySet.contains(ComponentProperties.FONT)) {
+            label.setFont((Font) ((ValueChange) m.get(ComponentProperties.FONT)).getNewValue());
+          }
+
+          if (keySet.contains(ComponentProperties.FOREGROUND_COLOR)) {
+            Color c = (Color) ((ValueChange) m.get(ComponentProperties.FOREGROUND_COLOR)).getNewValue();
+            label.setForeground(c);
+            setForeground(c);
+          }
+
+          if (keySet.contains(ComponentProperties.BACKGROUND_COLOR)) {
+            Color c = (Color) ((ValueChange) m.get(ComponentProperties.BACKGROUND_COLOR)).getNewValue();
+            panel.setBackground(c);
+          }
+
+          if (keySet.contains(ComponentProperties.INSETS) || keySet.contains(ComponentProperties.BORDER)) {
+            updateBorders = true;
+          }
+        }
+
+        m = (Map) changes.get(stateProperties.getShapedPanelProperties().getMap());
+        if (m != null) {
+          updateShapedPanel(stateProperties);
+        }
+      }
+
+      return updateBorders;
+    }
+  }
+
+  private final TitledTabProperties properties = TitledTabProperties.getDefaultProperties();
+
+  private HoverListener hoverListener = properties.getHoverListener();
+
+  private class HoverablePanel extends SimplePanel implements Hoverable {
+    public HoverablePanel(LayoutManager l) {
+      super(l);
+    }
+
+    public void hoverEnter() {
+      if (hoverListener != null && getTabbedPanel() != null)
+        hoverListener.mouseEntered(new HoverEvent(TitledTab.this));
+    }
+
+    public void hoverExit() {
+      if (hoverListener != null)
+        hoverListener.mouseExited(new HoverEvent(TitledTab.this));
+    }
+
+    public boolean acceptHover(ArrayList enterableHoverables) {
+      return true;
+    }
+  }
+
+  private final HoverablePanel eventPanel = new HoverablePanel(new BorderLayout()) {
+
+    public boolean contains(int x, int y) {
+      return getComponentCount() > 0 && getComponent(0).contains(x, y);
+    }
+
+    public boolean inside(int x, int y) {
+      return getComponentCount() > 0 && getComponent(0).inside(x, y);
+    }
+
+  };
+
+  public boolean contains(int x, int y) {
+    Point p = SwingUtilities.convertPoint(this, new Point(x, y), eventPanel);
+    return eventPanel.contains(p.x, p.y);
+  }
+
+  public boolean inside(int x, int y) {
+    Point p = SwingUtilities.convertPoint(this, new Point(x, y), eventPanel);
+    return eventPanel.inside(p.x, p.y);
+  }
+
+  private final StatePanel normalStatePanel;
+  private final StatePanel highlightedStatePanel;
+  private final StatePanel disabledStatePanel;
+
+  private ArrayList mouseListeners;
+  private ArrayList mouseMotionListeners;
+  private final StackableLayout layout;
+  private StatePanel currentStatePanel;
+  private final FocusBorder focusBorder;
+
+  private Direction lastTabAreaOrientation = Direction.UP;
+
+  private final PropertyMapTreeListener propertiesListener = new PropertyMapTreeListener() {
+    public void propertyValuesChanged(Map changes) {
+      doUpdateTab(changes);
+    }
+  };
+
+  private final PropertyChangeListener tabbedPanelPropertiesListener = new PropertyChangeListener() {
+    public void propertyChanged(Property property, Object valueContainer, Object oldValue, Object newValue) {
+      updateTabAreaOrientation((Direction) newValue);
+    }
+  };
+
+  /*  private FocusListener focusListener = new FocusListener() {
+    public void focusGained(FocusEvent e) {
+      if (properties.getFocusable())
+        repaint();
+    }
+
+    public void focusLost(FocusEvent e) {
+      if (properties.getFocusable())
+        repaint();
+    }
+  };*/
+
+  /**
+   * Constructs a TitledTab with a text, icon, content component and title component.
+   *
+   * @param text             text or null for no text. The text will be applied to the
+   *                         normal state properties
+   * @param icon             icon or null for no icon. The icon will be applied to the
+   *                         normal state properties
+   * @param contentComponent content component or null for no content component
+   * @param titleComponent   title component or null for no title component. The title
+   *                         component will be applied to all the states
+   * @see net.infonode.tabbedpanel.TabFactory
+   */
+  public TitledTab(String text, Icon icon, JComponent contentComponent, JComponent titleComponent) {
+    super(contentComponent);
+    super.setOpaque(false);
+
+    addFocusListener(new FocusListener() {
+      public void focusGained(FocusEvent e) {
+        repaint();
+      }
+
+      public void focusLost(FocusEvent e) {
+        repaint();
+      }
+    });
+
+    focusBorder = new FocusBorder(this);
+    normalStatePanel = new StatePanel(focusBorder);
+    highlightedStatePanel = new StatePanel(focusBorder);
+    disabledStatePanel = new StatePanel(focusBorder);
+
+
+    layout = new StackableLayout(this) {
+      public void layoutContainer(Container parent) {
+        super.layoutContainer(parent);
+        StatePanel visibleStatePanel = (StatePanel) getVisibleComponent();
+        visibleStatePanel.activateTitleComponent();
+      }
+    };
+
+    setLayout(layout);
+
+    add(normalStatePanel);
+    add(highlightedStatePanel);
+    add(disabledStatePanel);
+
+    setText(text);
+    setIcon(icon);
+    setTitleComponent(titleComponent);
+
+    eventPanel.addMouseListener(new MouseAdapter() {
+      public void mousePressed(MouseEvent e) {
+        updateFocus(TabSelectTrigger.MOUSE_PRESS);
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        updateFocus(TabSelectTrigger.MOUSE_RELEASE);
+      }
+
+      private void updateFocus(TabSelectTrigger trigger) {
+        if (isEnabled() && properties.getFocusable() && getTabbedPanel() != null && getTabbedPanel().getProperties()
+            .getTabSelectTrigger() == trigger) {
+          Component focusedComponent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
+
+          if (focusedComponent instanceof TitledTab
+              && ((TitledTab) focusedComponent).getTabbedPanel() == getTabbedPanel())
+            requestFocusInWindow();
+          else if (isSelected() || TabbedUtils.getParentTabbedPanel(focusedComponent) != getTabbedPanel())
+            requestFocusInWindow();
+        }
+      }
+    });
+
+    setEventComponent(eventPanel);
+
+    MouseListener mouseListener = new MouseListener() {
+      public void mouseClicked(MouseEvent e) {
+        if (mouseListeners != null) {
+          MouseEvent event = convertMouseEvent(e);
+          Object[] l = mouseListeners.toArray();
+          for (int i = 0; i < l.length; i++)
+            ((MouseListener) l[i]).mouseClicked(event);
+        }
+      }
+
+      public void mousePressed(MouseEvent e) {
+        if (mouseListeners != null) {
+          MouseEvent event = convertMouseEvent(e);
+          Object[] l = mouseListeners.toArray();
+          for (int i = 0; i < l.length; i++)
+            ((MouseListener) l[i]).mousePressed(event);
+        }
+      }
+
+      public void mouseReleased(MouseEvent e) {
+        if (mouseListeners != null) {
+          MouseEvent event = convertMouseEvent(e);
+          Object[] l = mouseListeners.toArray();
+          for (int i = 0; i < l.length; i++)
+            ((MouseListener) l[i]).mouseReleased(event);
+        }
+      }
+
+      public void mouseEntered(MouseEvent e) {
+        if (mouseListeners != null) {
+          MouseEvent event = convertMouseEvent(e);
+          Object[] l = mouseListeners.toArray();
+          for (int i = 0; i < l.length; i++)
+            ((MouseListener) l[i]).mouseEntered(event);
+        }
+      }
+
+      public void mouseExited(MouseEvent e) {
+        if (mouseListeners != null) {
+          MouseEvent event = convertMouseEvent(e);
+          Object[] l = mouseListeners.toArray();
+          for (int i = 0; i < l.length; i++)
+            ((MouseListener) l[i]).mouseExited(event);
+        }
+      }
+    };
+
+    MouseMotionListener mouseMotionListener = new MouseMotionListener() {
+      public void mouseDragged(MouseEvent e) {
+        if (mouseMotionListeners != null) {
+          MouseEvent event = convertMouseEvent(e);
+          Object[] l = mouseMotionListeners.toArray();
+          for (int i = 0; i < l.length; i++)
+            ((MouseMotionListener) l[i]).mouseDragged(event);
+        }
+      }
+
+      public void mouseMoved(MouseEvent e) {
+        if (mouseMotionListeners != null) {
+          MouseEvent event = convertMouseEvent(e);
+          Object[] l = mouseMotionListeners.toArray();
+          for (int i = 0; i < l.length; i++)
+            ((MouseMotionListener) l[i]).mouseMoved(event);
+        }
+      }
+    };
+
+    eventPanel.addMouseListener(mouseListener);
+    eventPanel.addMouseMotionListener(mouseMotionListener);
+
+    PropertyMapWeakListenerManager.addWeakTreeListener(properties.getMap(), propertiesListener);
+
+    addTabListener(new TabAdapter() {
+      public void tabAdded(TabEvent event) {
+        PropertyMapWeakListenerManager.addWeakPropertyChangeListener(getTabbedPanel().getProperties().getMap(),
+            TabbedPanelProperties.TAB_AREA_ORIENTATION,
+            tabbedPanelPropertiesListener);
+        updateTabAreaOrientation(getTabbedPanel().getProperties().getTabAreaOrientation());
+      }
+
+      public void tabRemoved(TabRemovedEvent event) {
+        PropertyMapWeakListenerManager.removeWeakPropertyChangeListener(
+            event.getTabbedPanel().getProperties().getMap(),
+            TabbedPanelProperties.TAB_AREA_ORIENTATION,
+            tabbedPanelPropertiesListener);
+      }
+    });
+
+    doUpdateTab(null);
+    updateCurrentStatePanel();
+  }
+
+  /**
+   * Gets the title component for the normal state
+   *
+   * @return title component or null if no title component
+   */
+  public JComponent getNormalStateTitleComponent() {
+    return normalStatePanel.getTitleComponent();
+  }
+
+  /**
+   * Gets the title component for the highlighted state
+   *
+   * @return title component or null if no title component
+   */
+  public JComponent getHighlightedStateTitleComponent() {
+    return highlightedStatePanel.getTitleComponent();
+  }
+
+  /**
+   * Gets the title component for the disabled state
+   *
+   * @return title component or null if no title component
+   */
+  public JComponent getDisabledStateTitleComponent() {
+    return disabledStatePanel.getTitleComponent();
+  }
+
+  /**
+   * <p>Sets the title component.</p>
+   *
+   * <p>This method is a convenience method for setting the same title component for
+   * all states.</p>
+   *
+   * @param titleComponent the title component or null for no title component
+   */
+  public void setTitleComponent(JComponent titleComponent) {
+    normalStatePanel.setTitleComponent(titleComponent, properties.getNormalProperties());
+    highlightedStatePanel.setTitleComponent(titleComponent, properties.getHighlightedProperties());
+    disabledStatePanel.setTitleComponent(titleComponent, properties.getDisabledProperties());
+  }
+
+  /**
+   * Sets the normal state title component
+   *
+   * @param titleComponent the title component or null for no title component
+   */
+  public void setNormalStateTitleComponent(JComponent titleComponent) {
+    normalStatePanel.setTitleComponent(titleComponent, properties.getNormalProperties());
+  }
+
+  /**
+   * Sets the highlighted state title component
+   *
+   * @param titleComponent the title component or null for no title component
+   */
+  public void setHighlightedStateTitleComponent(JComponent titleComponent) {
+    highlightedStatePanel.setTitleComponent(titleComponent, properties.getHighlightedProperties());
+  }
+
+  /**
+   * Sets the disabled state title component
+   *
+   * @param titleComponent the title component or null for no title component
+   */
+  public void setDisabledStateTitleComponent(JComponent titleComponent) {
+    disabledStatePanel.setTitleComponent(titleComponent, properties.getDisabledProperties());
+  }
+
+  /**
+   * <p>Sets if this TitledTab should be highlighted or not.</p>
+   *
+   * <p><strong>Note:</strong> This will only have effect if this TitledTab
+   * is enabled and a member of a tabbed panel.</p>
+   *
+   * @param highlighted true for highlight, otherwise false
+   */
+  public void setHighlighted(boolean highlighted) {
+    super.setHighlighted(highlighted);
+    updateCurrentStatePanel();
+  }
+
+  /**
+   * <p>
+   * Sets if this TitledTab should be enabled or disabled
+   * </p>
+   * 
+   * <p>
+   * <strong>Note:</strong> since ITP 1.5.0 this method will change the enabled property
+   * in the {@link TitledTabProperties} for this tab. Enabled/disabled can be controlled by
+   * modifying the property or this method.
+   * </p>
+   *
+   * @param enabled true for enabled, otherwise false
+   */
+  public void setEnabled(boolean enabled) {
+    super.setEnabled(enabled);
+    updateCurrentStatePanel();
+  }
+
+  /**
+   * Gets the text for the normal state
+   *
+   * @return the text or null if no text
+   */
+  public String getText() {
+    return properties.getNormalProperties().getText();
+  }
+
+  /**
+   * Sets the text for the normal state
+   *
+   * @param text the text or null for no text
+   */
+  public void setText(String text) {
+    properties.getNormalProperties().setText(text);
+  }
+
+  /**
+   * Gets the icon for the normal state
+   *
+   * @return the icon or null if none
+   */
+  public Icon getIcon() {
+    return properties.getNormalProperties().getIcon();
+  }
+
+  /**
+   * Sets the icon for the normal state
+   *
+   * @param icon the icon or null for no icon
+   */
+  public void setIcon(Icon icon) {
+    properties.getNormalProperties().setIcon(icon);
+  }
+
+  /**
+   * Gets the TitledTabProperties
+   *
+   * @return the TitledTabProperties for this TitledTab
+   */
+  public TitledTabProperties getProperties() {
+    return properties;
+  }
+
+  /**
+   * Gets the text for the normal state.
+   *
+   * Same as getText().
+   *
+   * @return the text or null if no text
+   * @see #getText
+   * @since ITP 1.1.0
+   */
+  public String toString() {
+    return getText();
+  }
+
+  /**
+   * Adds a MouseListener to receive mouse events from this TitledTab.
+   *
+   * @param l the MouseListener
+   */
+  public synchronized void addMouseListener(MouseListener l) {
+    if (mouseListeners == null)
+      mouseListeners = new ArrayList(2);
+    mouseListeners.add(l);
+  }
+
+  /**
+   * Removes a MouseListener
+   *
+   * @param l the MouseListener to remove
+   */
+  public synchronized void removeMouseListener(MouseListener l) {
+    if (mouseListeners != null) {
+      mouseListeners.remove(l);
+
+      if (mouseListeners.size() == 0)
+        mouseListeners = null;
+    }
+  }
+
+  /**
+   * Gets the mouse listeners
+   *
+   * @return the mouse listeners
+   */
+  public synchronized MouseListener[] getMouseListeners() {
+    MouseListener[] listeners = new MouseListener[0];
+
+    if (mouseListeners != null) {
+      Object[] l = mouseListeners.toArray();
+      listeners = new MouseListener[l.length];
+      for (int i = 0; i < l.length; i++)
+        listeners[i] = (MouseListener) l[i];
+    }
+
+    return listeners;
+  }
+
+  /**
+   * Adds a MouseMotionListener to receive mouse events from this TitledTab.
+   *
+   * @param l the MouseMotionListener
+   */
+  public synchronized void addMouseMotionListener(MouseMotionListener l) {
+    if (mouseMotionListeners == null)
+      mouseMotionListeners = new ArrayList(2);
+
+    mouseMotionListeners.add(l);
+  }
+
+  /**
+   * Removes a MouseMotionListener
+   *
+   * @param l the MouseMotionListener to remove
+   */
+  public synchronized void removeMouseMotionListener(MouseMotionListener l) {
+    if (mouseMotionListeners != null) {
+      mouseMotionListeners.remove(l);
+
+      if (mouseMotionListeners.size() == 0)
+        mouseMotionListeners = null;
+    }
+  }
+
+  /**
+   * Gets the mouse motion listeners
+   *
+   * @return the mouse motion listeners
+   */
+  public synchronized MouseMotionListener[] getMouseMotionListeners() {
+    MouseMotionListener[] listeners = new MouseMotionListener[0];
+
+    if (mouseMotionListeners != null) {
+      Object[] l = mouseMotionListeners.toArray();
+      listeners = new MouseMotionListener[l.length];
+      for (int i = 0; i < l.length; i++)
+        listeners[i] = (MouseMotionListener) l[i];
+    }
+
+    return listeners;
+  }
+
+  /**
+   * Gets the Shape for the current active rendering state.
+   *
+   * @return the Shape for the active rendering state, null if no special shape
+   * @since ITP 1.2.0
+   */
+  public Shape getShape() {
+    Shape shape = currentStatePanel.getShape();
+
+    if (shape == null)
+      return null;
+
+    Point p = SwingUtilities.convertPoint(currentStatePanel, 0, 0, this);
+    return new TranslatingShape(shape, p.x, p.y);
+  }
+
+  protected void setTabbedPanel(TabbedPanel tabbedPanel) {
+    if (tabbedPanel == null)
+      HoverManager.getInstance().removeHoverable(eventPanel);
+
+    super.setTabbedPanel(tabbedPanel);
+
+    if (tabbedPanel != null)
+      HoverManager.getInstance().addHoverable(eventPanel);
+  }
+
+  private Insets getBorderInsets(Border border) {
+    return border == null ? InsetsUtil.EMPTY_INSETS : border.getBorderInsets(this);
+  }
+
+  private void updateBorders() {
+    Direction tabAreaOrientation = getTabAreaOrientation();
+    int raised = properties.getHighlightedRaised();
+    Insets notRaised = InsetsUtil.setInset(InsetsUtil.EMPTY_INSETS, tabAreaOrientation, raised);
+    Border normalBorder = new EmptyBorder(notRaised);
+
+    Insets maxInsets = properties.getBorderSizePolicy() == TitledTabBorderSizePolicy.INDIVIDUAL_SIZE ?
+                                                                                                      null :
+                                                                                                        InsetsUtil.max(
+                                                                                                            getBorderInsets(properties.getNormalProperties().getComponentProperties().getBorder()),
+                                                                                                            InsetsUtil.max(getBorderInsets(
+                                                                                                                properties.getHighlightedProperties().getComponentProperties().getBorder()),
+                                                                                                                getBorderInsets(
+                                                                                                                    properties.getDisabledProperties().getComponentProperties().getBorder())));
+
+    Insets normalInsets = InsetsUtil.rotate(properties.getNormalProperties().getDirection(),
+        properties.getNormalProperties().getComponentProperties().getInsets());
+
+    Insets disabledInsets = InsetsUtil.rotate(properties.getDisabledProperties().getDirection(),
+        properties.getDisabledProperties().getComponentProperties().getInsets());
+
+    int edgeInset = Math.min(InsetsUtil.getInset(normalInsets,
+        tabAreaOrientation.getOpposite()),
+        InsetsUtil.getInset(disabledInsets,
+            tabAreaOrientation.getOpposite()));
+
+    int normalLowered = Math.min(edgeInset, raised);
+
+    Border innerNormalBorder = getInnerBorder(properties.getNormalProperties(),
+        tabAreaOrientation,
+        -normalLowered,
+        maxInsets);
+    Border innerHighlightBorder = getInnerBorder(properties.getHighlightedProperties(),
+        tabAreaOrientation,
+        raised - normalLowered,
+        maxInsets);
+    Border innerDisabledBorder = getInnerBorder(properties.getDisabledProperties(),
+        tabAreaOrientation,
+        -normalLowered,
+        maxInsets);
+
+    normalStatePanel.setBorders(normalBorder, innerNormalBorder);
+    highlightedStatePanel.setBorders(null, innerHighlightBorder);
+    disabledStatePanel.setBorders(normalBorder, innerDisabledBorder);
+  }
+
+  private void doUpdateTab(Map changes) {
+    boolean updateBorders = false;
+
+    if (changes == null) {
+      // Init all
+      updateBorders = true;
+
+      setFocusableComponent(properties.getFocusable() ? this : null);
+      focusBorder.setEnabled(properties.getFocusMarkerEnabled());
+
+      updateHoverListener(properties.getHoverListener());
+      layout.setUseSelectedComponentSize(properties.getSizePolicy() == TitledTabSizePolicy.INDIVIDUAL_SIZE);
+    }
+    else {
+      Map m = (Map) changes.get(properties.getMap());
+      if (m != null) {
+        Set keySet = m.keySet();
+
+        if (keySet.contains(TitledTabProperties.FOCUSABLE)) {
+          setFocusableComponent(properties.getFocusable() ? this : null);
+        }
+
+        if (keySet.contains(TitledTabProperties.FOCUS_MARKER_ENABLED)) {
+          focusBorder.setEnabled(properties.getFocusMarkerEnabled());
+          currentStatePanel.getLabel().repaint();
+        }
+
+        if (keySet.contains(TitledTabProperties.HOVER_LISTENER)) {
+          updateHoverListener((HoverListener) ((ValueChange) m.get(TitledTabProperties.HOVER_LISTENER)).getNewValue());
+        }
+
+        if (keySet.contains(TitledTabProperties.SIZE_POLICY)) {
+          layout.setUseSelectedComponentSize(
+              ((TitledTabSizePolicy) ((ValueChange) m.get(TitledTabProperties.SIZE_POLICY)).getNewValue()) == TitledTabSizePolicy.INDIVIDUAL_SIZE);
+        }
+
+        if (keySet.contains(TitledTabProperties.HIGHLIGHTED_RAISED_AMOUNT) || keySet.contains(
+            TitledTabProperties.BORDER_SIZE_POLICY)) {
+          updateBorders = true;
+        }
+
+        if (keySet.contains(TitledTabProperties.ENABLED)) {
+          doSetEnabled(properties.getEnabled());
+        }
+      }
+    }
+
+    updateBorders = normalStatePanel.updateState(changes, properties.getNormalProperties()) || updateBorders;
+    updateBorders = highlightedStatePanel.updateState(changes, properties.getHighlightedProperties()) || updateBorders;
+    updateBorders = disabledStatePanel.updateState(changes, properties.getDisabledProperties()) || updateBorders;
+
+    if (updateBorders)
+      updateBorders();
+  }
+
+  private void updateHoverListener(HoverListener newHoverListener) {
+    HoverListener oldHoverListener = hoverListener;
+    hoverListener = newHoverListener;
+    if (HoverManager.getInstance().isHovered(eventPanel)) {
+      if (oldHoverListener != null)
+        oldHoverListener.mouseExited(new HoverEvent(TitledTab.this));
+      if (hoverListener != null)
+        hoverListener.mouseEntered(new HoverEvent(TitledTab.this));
+    }
+  }
+
+  private Border getInnerBorder(TitledTabStateProperties properties,
+                                Direction tabOrientation,
+                                int raised,
+                                Insets maxInsets) {
+    Direction tabDir = properties.getDirection();
+    Insets insets = InsetsUtil.rotate(tabDir, properties.getComponentProperties().getInsets());
+
+    if (maxInsets != null)
+      insets = InsetsUtil.add(insets,
+          InsetsUtil.sub(maxInsets,
+              getBorderInsets(properties.getComponentProperties().getBorder())));
+
+    Border border = properties.getComponentProperties().getBorder();
+    Border innerBorder = new EmptyBorder(InsetsUtil.add(insets,
+        InsetsUtil.setInset(InsetsUtil.EMPTY_INSETS,
+            tabOrientation.getOpposite(),
+            raised)));
+    return border == null ? innerBorder : new CompoundBorder(border, innerBorder);
+  }
+
+  private Direction getTabAreaOrientation() {
+    return getTabbedPanel() == null ?
+                                     lastTabAreaOrientation : getTabbedPanel().getProperties().getTabAreaOrientation();
+  }
+
+  private void updateTabAreaOrientation(Direction newDirection) {
+    if (lastTabAreaOrientation != newDirection) {
+      lastTabAreaOrientation = newDirection;
+      updateBorders();
+
+      normalStatePanel.updateShapedPanel(properties.getNormalProperties());
+      highlightedStatePanel.updateShapedPanel(properties.getHighlightedProperties());
+      disabledStatePanel.updateShapedPanel(properties.getDisabledProperties());
+    }
+  }
+
+  private void updateCurrentStatePanel() {
+    StatePanel newStatePanel = normalStatePanel;
+    if (!isEnabled())
+      newStatePanel = disabledStatePanel;
+    else if (isHighlighted())
+      newStatePanel = highlightedStatePanel;
+
+    eventPanel.setToolTipText(newStatePanel.getToolTipText());
+
+    if (currentStatePanel != newStatePanel) {
+      if (currentStatePanel != null)
+        currentStatePanel.deactivate();
+      currentStatePanel = newStatePanel;
+      currentStatePanel.activate();
+    }
+    layout.showComponent(currentStatePanel);
+  }
+
+  private MouseEvent convertMouseEvent(MouseEvent e) {
+    Point p = SwingUtilities.convertPoint((JComponent) e.getSource(), e.getPoint(), TitledTab.this);
+    return new MouseEvent(TitledTab.this, e.getID(), e.getWhen(), e.getModifiers(),
+        (int) p.getX(), (int) p.getY(), e.getClickCount(),
+        !e.isConsumed() && e.isPopupTrigger(), e.getButton());
+  }
+
+  private void doSetEnabled(boolean enabled) {
+    super.setEnabled(enabled);
+    updateCurrentStatePanel();
+  }
+
+  public void setUI(PanelUI ui) {
+    if (getUI() != UI)
+      super.setUI(UI);
+  }
+
+  public void updateUI() {
+    setUI(UI);
+  }
+
+  public void setOpaque(boolean opaque) {
+    // Ignore
+  }
+
+}
\ No newline at end of file
diff --git a/src/net/infonode/tabbedpanel/titledtab/TitledTabBorderSizePolicy.java b/src/net/infonode/tabbedpanel/titledtab/TitledTabBorderSizePolicy.java
new file mode 100644
index 0000000..a3040b5
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/titledtab/TitledTabBorderSizePolicy.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitledTabBorderSizePolicy.java,v 1.8 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel.titledtab;
+
+import net.infonode.util.Enum;
+
+/**
+ * TitledTabBorderSizePolicy defines how the insets for the titled tab
+ * should be calculated based on the borders for the different tab states.
+ * If the states have borders with different insets titled tab can use the same
+ * insets for each state based on the maximum insets (top, left, bottom,
+ * right) for each state. The compensated insets will be added on the inside
+ * of the borders.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.8 $
+ * @see TitledTab
+ * @see TitledTabProperties
+ */
+public final class TitledTabBorderSizePolicy extends Enum {
+  private static final long serialVersionUID = 5570620861429391549L;
+
+  /**
+   * Equal size policy. This means that if the different tab states have
+   * borders with different insets titled tab will use the same insets for
+   * each state based on the maximum insets (top, left, bottom, right)
+   * for each state. The compensated insets will be added on the inside of
+   * the borders.
+   */
+  public static final TitledTabBorderSizePolicy EQUAL_SIZE = new TitledTabBorderSizePolicy(0, "Equal Size");
+
+  /**
+   * Individual size policy. This means that titled tab will use the borders
+   * for each state as they are and not modify any insets. If the borders for
+   * the different states have different insets, then the titled tab's insets
+   * will be different depending on the state the tab is currently in.
+   */
+  public static final TitledTabBorderSizePolicy INDIVIDUAL_SIZE = new TitledTabBorderSizePolicy(1, "Individual Size");
+
+  /**
+   * An array with all size policies
+   */
+  public static final TitledTabBorderSizePolicy[] SIZE_POLICIES = new TitledTabBorderSizePolicy[]{EQUAL_SIZE,
+                                                                                                  INDIVIDUAL_SIZE};
+
+  private TitledTabBorderSizePolicy(int value, String name) {
+    super(value, name);
+  }
+
+  /**
+   * Gets the titled tab border size policies.
+   *
+   * @return the titled tab border size policies
+   * @since ITP 1.1.0
+   */
+  public static TitledTabBorderSizePolicy[] getSizePolicies() {
+    return (TitledTabBorderSizePolicy[]) SIZE_POLICIES.clone();
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/titledtab/TitledTabBorderSizePolicyProperty.java b/src/net/infonode/tabbedpanel/titledtab/TitledTabBorderSizePolicyProperty.java
new file mode 100644
index 0000000..7866a48
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/titledtab/TitledTabBorderSizePolicyProperty.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitledTabBorderSizePolicyProperty.java,v 1.7 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel.titledtab;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.types.EnumProperty;
+import net.infonode.properties.util.PropertyValueHandler;
+
+/**
+ * Property for TitledTabBorderSizePolicy
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ * @see TitledTabBorderSizePolicy
+ */
+public class TitledTabBorderSizePolicyProperty extends EnumProperty {
+  /**
+   * Constructs a TitledTabBorderSizePolicyProperty object.
+   *
+   * @param group        property group
+   * @param name         property name
+   * @param description  property description
+   * @param valueStorage storage for property
+   */
+  public TitledTabBorderSizePolicyProperty(PropertyGroup group,
+                                           String name,
+                                           String description,
+                                           PropertyValueHandler valueStorage) {
+    super(group,
+          name,
+          TitledTabBorderSizePolicy.class,
+          description,
+          valueStorage,
+          TitledTabBorderSizePolicy.getSizePolicies());
+  }
+
+  /**
+   * Gets the TitledTabBorderSizePolicy
+   *
+   * @param object storage object for property
+   * @return the TitledTabBorderSizePolicy
+   */
+  public TitledTabBorderSizePolicy get(Object object) {
+    return (TitledTabBorderSizePolicy) getValue(object);
+  }
+
+  /**
+   * Sets the TitledTabBorderSizePolicy
+   *
+   * @param object storage object for property
+   * @param policy the TitledTabBorderSizePolicy
+   */
+  public void set(Object object, TitledTabBorderSizePolicy policy) {
+    setValue(object, policy);
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/titledtab/TitledTabProperties.java b/src/net/infonode/tabbedpanel/titledtab/TitledTabProperties.java
new file mode 100644
index 0000000..76cabba
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/titledtab/TitledTabProperties.java
@@ -0,0 +1,618 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+// $Id: TitledTabProperties.java,v 1.42 2007/01/28 21:25:49 jesper Exp $
+package net.infonode.tabbedpanel.titledtab;
+
+import net.infonode.gui.DimensionProvider;
+import net.infonode.gui.DynamicUIManager;
+import net.infonode.gui.DynamicUIManagerListener;
+import net.infonode.gui.hover.HoverListener;
+import net.infonode.properties.base.Property;
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.BooleanProperty;
+import net.infonode.properties.types.DimensionProviderProperty;
+import net.infonode.properties.types.HoverListenerProperty;
+import net.infonode.properties.types.IntegerProperty;
+import net.infonode.tabbedpanel.TabbedUIDefaults;
+import net.infonode.tabbedpanel.border.TabAreaLineBorder;
+import net.infonode.tabbedpanel.border.TabHighlightBorder;
+import net.infonode.util.Alignment;
+import net.infonode.util.Direction;
+
+import javax.swing.border.CompoundBorder;
+
+/**
+ * <p>
+ * TitledTabProperties holds all properties for a {@link TitledTab}.
+ * </p>
+ *
+ * <p>
+ * A titled tab can have three states, normal, highlighted and disabled. Each state is represented by a {@link
+ * TitledTabStateProperties} object containing all properties that can be set for a state.
+ * </p>
+ *
+ * <p>
+ * By default the property values in the highlighted and disabled state are references to corresponding values in the
+ * normal state. This means that if you set a property value in the normal state, then highlighted and the disabled
+ * state will use that property value if the property has not been set in the highlighted or disabled state.
+ * </p>
+ *
+ * <p>
+ * Example:<br> Setting the background color in the normal state means that normal, highlighted and disabled state will
+ * use that color as background color. If you set background color for highlighted state, then the highlighted state
+ * will use that color regardless of the background color for the normal state.
+ * </p>
+ *
+ * <p>
+ * By default the tool tip text in all states is the same as the tab text in the normal state. For example, if you
+ * change the tab text in the highlighted state and want the tooltip to display the same text, you must set the "Tool
+ * Tip Text" property {@link TitledTabStateProperties#TOOL_TIP_TEXT} in the highlighted state.
+ * </p>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.42 $
+ * @see TitledTab
+ * @see TitledTabStateProperties
+ */
+public class TitledTabProperties extends PropertyMapContainer {
+  /**
+   * A property group for all properties in TitledTabProperties
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("Titled Tab Properties",
+                                                                         "Properties for the TitledTab class.");
+
+  /**
+   * Focusabled property
+   *
+   * @see #setFocusable
+   * @see #getFocusable
+   */
+  public static final BooleanProperty FOCUSABLE = new BooleanProperty(PROPERTIES, "Focusable", "Tab focusable",
+                                                                      PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Focus Marker Enabled property
+   *
+   * @see #setFocusMarkerEnabled
+   * @see #getFocusMarkerEnabled
+   * @since ITP 1.4.0
+   */
+  public static final BooleanProperty FOCUS_MARKER_ENABLED = new BooleanProperty(PROPERTIES, "Focus Marker Enabled", "Enables or disables the focus marker when the tab has focus.",
+                                                                                 PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Normal state properties
+   *
+   * @see #getNormalProperties
+   */
+  public static final PropertyMapProperty NORMAL_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                      "Normal Properties",
+                                                                                      "Normal tab properties.",
+                                                                                      TitledTabStateProperties.PROPERTIES);
+
+  /**
+   * Highlighted state properties
+   *
+   * @see #getHighlightedProperties
+   */
+  public static final PropertyMapProperty HIGHLIGHTED_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                           "Highlighted Properties",
+                                                                                           "Highlighted tab properties.",
+                                                                                           TitledTabStateProperties.PROPERTIES);
+
+  /**
+   * Disabled state properties
+   *
+   * @see #getDisabledProperties
+   */
+  public static final PropertyMapProperty DISABLED_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                        "Disabled Properties",
+                                                                                        "Disabled tab properties.",
+                                                                                        TitledTabStateProperties.PROPERTIES);
+
+  /**
+   * Size policy property
+   *
+   * @see #setSizePolicy
+   * @see #getSizePolicy
+   */
+  public static final TitledTabSizePolicyProperty SIZE_POLICY = new TitledTabSizePolicyProperty(PROPERTIES,
+                                                                                                "Size Policy",
+                                                                                                "Tab size policy",
+                                                                                                PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Border size policy property
+   *
+   * @see #setBorderSizePolicy
+   * @see #getBorderSizePolicy
+   */
+  public static final TitledTabBorderSizePolicyProperty BORDER_SIZE_POLICY = new TitledTabBorderSizePolicyProperty(
+      PROPERTIES,
+      "Border Size Policy",
+      "Border size policy.",
+      PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tab minimum size property
+   *
+   * @see #setMinimumSizeProvider(DimensionProvider)
+   * @see #getMinimumSizeProvider()
+   */
+  public static final DimensionProviderProperty MINIMUM_SIZE_PROVIDER = new DimensionProviderProperty(PROPERTIES,
+                                                                                                      "Minimum Size",
+                                                                                                      "Tab minimum size.",
+                                                                                                      PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Highlighted raised amount property
+   *
+   * @see #setHighlightedRaised
+   * @see #getHighlightedRaised
+   */
+  public static final IntegerProperty HIGHLIGHTED_RAISED_AMOUNT = IntegerProperty.createPositive(PROPERTIES,
+                                                                                                 "Highlighted Raised",
+                                                                                                 "Number of raised pixels for highlighted tab.",
+                                                                                                 2,
+                                                                                                 PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Hover listener property
+   *
+   * @see #setHoverListener
+   * @see #getHoverListener
+   * @since ITP 1.3.0
+   */
+  public static final HoverListenerProperty HOVER_LISTENER = new HoverListenerProperty(PROPERTIES,
+                                                                                       "Hover Listener",
+                                                                                       "Hover Listener to be used for tracking mouse hovering over the tab.",
+                                                                                       PropertyMapValueHandler.INSTANCE);
+  /**
+   * TitledTab enabled property
+   *
+   * @see #setEnabled
+   * @see #getEnabled
+   * @since ITP 1.5.0
+   */
+  public static final BooleanProperty ENABLED = new BooleanProperty(PROPERTIES,
+                                                                       "Enabled",
+                                                                       "TitledTab enabled or disabled",
+                                                                        PropertyMapValueHandler.INSTANCE);
+
+  private static final TitledTabProperties DEFAULT_VALUES = new TitledTabProperties(PROPERTIES.getDefaultMap());
+
+  static {
+    DynamicUIManager.getInstance().addListener(new DynamicUIManagerListener() {
+      public void lookAndFeelChanged() {
+        updateVisualProperties();
+      }
+
+      public void propertiesChanged() {
+        updateVisualProperties();
+      }
+
+      public void propertiesChanging() {
+      }
+
+      public void lookAndFeelChanging() {
+      }
+    });
+
+    DEFAULT_VALUES.getNormalProperties().getMap().createRelativeRef(TitledTabStateProperties.TOOL_TIP_TEXT,
+                                                                    DEFAULT_VALUES.getNormalProperties().getMap(),
+                                                                    TitledTabStateProperties.TEXT);
+
+    DEFAULT_VALUES.getHighlightedProperties().getMap().addSuperMap(DEFAULT_VALUES.getNormalProperties().getMap());
+    DEFAULT_VALUES.getDisabledProperties().getMap().addSuperMap(DEFAULT_VALUES.getNormalProperties().getMap());
+
+    {
+      Property[] refProperties = TitledTabStateProperties.PROPERTIES.getProperties();
+
+      for (int i = 0; i < refProperties.length; i++) {
+        DEFAULT_VALUES.getHighlightedProperties().getMap().createRelativeRef(refProperties[i],
+                                                                             DEFAULT_VALUES.getNormalProperties()
+                                                                             .getMap(),
+                                                                             refProperties[i]);
+        DEFAULT_VALUES.getDisabledProperties().getMap().createRelativeRef(refProperties[i],
+                                                                          DEFAULT_VALUES.getNormalProperties().getMap(),
+                                                                          refProperties[i]);
+      }
+    }
+
+    {
+      Property[] refProperties = ComponentProperties.PROPERTIES.getProperties();
+
+      for (int i = 0; i < refProperties.length; i++) {
+        DEFAULT_VALUES.getHighlightedProperties().getComponentProperties().
+            getMap().createRelativeRef(refProperties[i],
+                                       DEFAULT_VALUES.getNormalProperties().getComponentProperties().getMap(),
+                                       refProperties[i]);
+        DEFAULT_VALUES.getDisabledProperties().getComponentProperties().
+            getMap().createRelativeRef(refProperties[i],
+                                       DEFAULT_VALUES.getNormalProperties().getComponentProperties().getMap(),
+                                       refProperties[i]);
+      }
+    }
+
+    {
+      Property[] refProperties = ShapedPanelProperties.PROPERTIES.getProperties();
+
+      for (int i = 0; i < refProperties.length; i++) {
+        DEFAULT_VALUES.getHighlightedProperties().getShapedPanelProperties().
+            getMap().createRelativeRef(refProperties[i],
+                                       DEFAULT_VALUES.getNormalProperties().getShapedPanelProperties().getMap(),
+                                       refProperties[i]);
+        DEFAULT_VALUES.getDisabledProperties().getShapedPanelProperties().
+            getMap().createRelativeRef(refProperties[i],
+                                       DEFAULT_VALUES.getNormalProperties().getShapedPanelProperties().getMap(),
+                                       refProperties[i]);
+      }
+    }
+
+    updateVisualProperties();
+    updateFunctionalProperties();
+  }
+
+  /**
+   * Constructs an empty TitledTabProperties object
+   */
+  public TitledTabProperties() {
+    super(PropertyMapFactory.create(PROPERTIES));
+  }
+
+  /**
+   * Constructs a TitledTabProperties object with the give object as property storage
+   *
+   * @param object object to store properties in
+   */
+  public TitledTabProperties(PropertyMap object) {
+    super(object);
+  }
+
+  /**
+   * Constructs a TitledTabProperties object that inherits its properties from the given TitledTabProperties object
+   *
+   * @param inheritFrom TitledTabProperties object to inherit properties from
+   */
+  public TitledTabProperties(TitledTabProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param superObject the object from which to inherit property values
+   * @return this
+   */
+  public TitledTabProperties addSuperObject(TitledTabProperties superObject) {
+    getMap().addSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   */
+  public TitledTabProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes the given super object.
+   *
+   * @param superObject super object to remove
+   * @return this
+   * @since ITP 1.3.0
+   */
+  public TitledTabProperties removeSuperObject(TitledTabProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Replaces the given super objects.
+   *
+   * @param oldSuperObject super object to replace
+   * @param newSuperObject new super object
+   * @return this
+   * @since ITP 1.4.0
+   */
+  public TitledTabProperties replaceSuperObject(TitledTabProperties oldSuperObject, TitledTabProperties newSuperObject) {
+    getMap().replaceSuperMap(oldSuperObject.getMap(), newSuperObject.getMap());
+    return this;
+  }
+
+  /**
+   * Creates a properties object with default properties based on the current look and feel
+   *
+   * @return properties object
+   */
+  public static TitledTabProperties getDefaultProperties() {
+    return new TitledTabProperties(DEFAULT_VALUES);
+  }
+
+  /**
+   * Gets the properties for the normal state
+   *
+   * @return the normal state properties
+   */
+  public TitledTabStateProperties getNormalProperties() {
+    return new TitledTabStateProperties(NORMAL_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the properties for the highlighted state
+   *
+   * @return the highlighted state properties
+   */
+  public TitledTabStateProperties getHighlightedProperties() {
+    return new TitledTabStateProperties(HIGHLIGHTED_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the properties for the disabled state
+   *
+   * @return the disabled state properties
+   */
+  public TitledTabStateProperties getDisabledProperties() {
+    return new TitledTabStateProperties(DISABLED_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Sets if this TitledTab should be focusable
+   *
+   * @param value true for focusable, otherwise false
+   * @return this TitledTabProperties
+   */
+  public TitledTabProperties setFocusable(boolean value) {
+    FOCUSABLE.set(getMap(), value);
+
+    return this;
+  }
+
+  /**
+   * Gets if this TitledTab is focusable
+   *
+   * @return true for focusable, otherwise false
+   */
+  public boolean getFocusable() {
+    return FOCUSABLE.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Sets if this TitledTab should show its built-in focus marker when this tab has focus.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> Disabling the focus marker is useful when for example creating a
+   * theme that draws its own focus marker.
+   * </p>
+   *
+   * @param value true for enabled, otherwise false
+   * @return this TitledTabProperties
+   * @since ITP 1.4.0
+   */
+  public TitledTabProperties setFocusMarkerEnabled(boolean value) {
+    FOCUS_MARKER_ENABLED.set(getMap(), value);
+
+    return this;
+  }
+
+  /**
+   * <p>
+   * Gets if this TitledTab should show its built-in focus marker when this tab has focus.
+   * </p>
+   *
+   * <p>
+   * <strong>Note:</strong> Disabling the focus marker is useful when for example creating a
+   * theme that draws its own focus marker.
+   * </p>
+   *
+   * @return true for enabled, otherwise false
+   * @since ITP 1.4.0
+   */
+  public boolean getFocusMarkerEnabled() {
+    return FOCUS_MARKER_ENABLED.get(getMap());
+  }
+
+  /**
+   * Sets the size policy for this TitledTab
+   *
+   * @param sizePolicy the size policy
+   * @return this TitledTabProperties
+   */
+  public TitledTabProperties setSizePolicy(TitledTabSizePolicy sizePolicy) {
+    SIZE_POLICY.set(getMap(), sizePolicy);
+
+    return this;
+  }
+
+  /**
+   * Gets the size policy for this TitledTab
+   *
+   * @return the size policy
+   */
+  public TitledTabSizePolicy getSizePolicy() {
+    return SIZE_POLICY.get(getMap());
+  }
+
+  /**
+   * Sets the border size policy for this TitledTab
+   *
+   * @param sizePolicy the border size policy
+   * @return this TitledTabProperties
+   */
+  public TitledTabProperties setBorderSizePolicy(TitledTabBorderSizePolicy sizePolicy) {
+    BORDER_SIZE_POLICY.set(getMap(), sizePolicy);
+
+    return this;
+  }
+
+  /**
+   * Gets the border size policy for this TitledTab
+   *
+   * @return the border size policy
+   */
+  public TitledTabBorderSizePolicy getBorderSizePolicy() {
+    return BORDER_SIZE_POLICY.get(getMap());
+  }
+
+  /**
+   * Sets the tab's minimum size dimension provider
+   *
+   * @param size the minimum size dimension provider or null if tab's default minimum size should be used instead
+   * @return this TitledTabProperties
+   */
+  public TitledTabProperties setMinimumSizeProvider(DimensionProvider size) {
+    MINIMUM_SIZE_PROVIDER.set(getMap(), size);
+
+    return this;
+  }
+
+  /**
+   * Gets the dimension provider for the tab's minimum size
+   *
+   * @return the minimum size provider or null if default tab minimum size is to be used instead
+   */
+  public DimensionProvider getMinimumSizeProvider() {
+    return MINIMUM_SIZE_PROVIDER.get(getMap());
+  }
+
+  /**
+   * Sets how many pixels higher this TitledTab will be when it is in its highlighted state compared to its normal and
+   * disabled state
+   *
+   * @param amount number of pixels
+   * @return this TitledTabProperties
+   */
+  public TitledTabProperties setHighlightedRaised(int amount) {
+    HIGHLIGHTED_RAISED_AMOUNT.set(getMap(), amount);
+
+    return this;
+  }
+
+  /**
+   * Gets how many pixels higher this TitledTab will be when it is in its highlighted state compared to its normal and
+   * disabled state
+   *
+   * @return number of pixels
+   */
+  public int getHighlightedRaised() {
+    return HIGHLIGHTED_RAISED_AMOUNT.get(getMap());
+  }
+
+  /**
+   * <p>
+   * Sets if this TitledTab should be enabled or not.
+   * </p>
+   * 
+   * <p>
+   * <strong>Note:</strong> Calling {@link TitledTab#setEnabled(boolean)} will modify this property for the tab.
+   * </p>
+   *
+   * @param value true for enabled, otherwise false
+   * @return this TitledTabProperties
+   * @since ITP 1.5.0
+   */
+  public TitledTabProperties setEnabled(boolean value) {
+    ENABLED.set(getMap(), value);
+
+    return this;
+  }
+
+  /**
+   * Gets if this TitledTab is enabled or disabled
+   *
+   * @return true for enabled, otherwise false
+   * @since ITP 1.5.0
+   */
+  public boolean getEnabled() {
+    return ENABLED.get(getMap());
+  }
+
+  /**
+   * <p>Sets the hover listener that will be triggered when the tab is hovered by the mouse.</p>
+   *
+   * <p>The hovered titled tab will be the source of the hover event sent to the hover listener.</p>
+   *
+   * @param listener the hover listener
+   * @return this TitledTabProperties
+   * @since ITP 1.3.0
+   */
+  public TitledTabProperties setHoverListener(HoverListener listener) {
+    HOVER_LISTENER.set(getMap(), listener);
+    return this;
+  }
+
+  /**
+   * <p>Gets the hover listener that will be triggered when the tab is hovered by the mouse.</p>
+   *
+   * <p>The hovered titled tab will be the source of the hover event sent to the hover listener.</p>
+   *
+   * @return the hover listener
+   * @since ITP 1.3.0
+   */
+  public HoverListener getHoverListener() {
+    return HOVER_LISTENER.get(getMap());
+  }
+
+  private static void updateVisualProperties() {
+    PropertyMapManager.runBatch(new Runnable() {
+      public void run() {
+        int gap = TabbedUIDefaults.getIconTextGap();
+
+        DEFAULT_VALUES.getNormalProperties().getShapedPanelProperties().setOpaque(true);
+
+        DEFAULT_VALUES.getNormalProperties().setIconTextGap(gap).setTextTitleComponentGap(gap)
+            .setIconVisible(true).setTextVisible(true).setTitleComponentVisible(true).getComponentProperties()
+            .setFont(TabbedUIDefaults.getFont())
+            .setForegroundColor(TabbedUIDefaults.getNormalStateForeground())
+            .setBackgroundColor(TabbedUIDefaults.getNormalStateBackground())
+            .setBorder(new TabAreaLineBorder())
+            .setInsets(TabbedUIDefaults.getTabInsets());
+
+        DEFAULT_VALUES.getHighlightedProperties().getComponentProperties()
+            .setBackgroundColor(TabbedUIDefaults.getHighlightedStateBackground())
+            .setBorder(new CompoundBorder(new TabAreaLineBorder(),
+                                          new TabHighlightBorder(TabbedUIDefaults.getHighlight(), true)));
+
+        DEFAULT_VALUES.getDisabledProperties().getComponentProperties()
+            .setForegroundColor(TabbedUIDefaults.getDisabledForeground()).setBackgroundColor(
+                TabbedUIDefaults.getDisabledBackground());
+      }
+    });
+  }
+
+  private static void updateFunctionalProperties() {
+    DEFAULT_VALUES.setEnabled(true).setFocusable(true).setFocusMarkerEnabled(true).setSizePolicy(TitledTabSizePolicy.EQUAL_SIZE)
+        .setBorderSizePolicy(TitledTabBorderSizePolicy.EQUAL_SIZE).setHighlightedRaised(2);
+
+    DEFAULT_VALUES.getNormalProperties().setHorizontalAlignment(Alignment.LEFT).setVerticalAlignment(Alignment.CENTER)
+        .setIconTextRelativeAlignment(Alignment.LEFT).setTitleComponentTextRelativeAlignment(Alignment.RIGHT)
+        .setDirection(Direction.RIGHT);
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/titledtab/TitledTabSizePolicy.java b/src/net/infonode/tabbedpanel/titledtab/TitledTabSizePolicy.java
new file mode 100644
index 0000000..341a552
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/titledtab/TitledTabSizePolicy.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitledTabSizePolicy.java,v 1.6 2004/09/28 15:07:29 jesper Exp $
+package net.infonode.tabbedpanel.titledtab;
+
+import net.infonode.util.Enum;
+
+/**
+ * TitledTabSizePolicy defines how TitledTab should calculate its size.
+ * If the different tab states results in different tab sizes, then TitledTab
+ * can calculate the maximum size for the states and use that size for all
+ * the states.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+public final class TitledTabSizePolicy extends Enum {
+  private static final long serialVersionUID = -7834501681762485226L;
+
+  /**
+   * Equal size policy. This menas that if the different tab states results in
+   * different tab sizes, then titled tab will calculate the maximum size for the
+   * states and use that size for all the states.
+   */
+  public static final TitledTabSizePolicy EQUAL_SIZE = new TitledTabSizePolicy(0, "Equal Size");
+
+  /**
+   * Individual size policy. This means that if the different tab states have
+   * different sizes then titled tab will have different size for the states.
+   */
+  public static final TitledTabSizePolicy INDIVIDUAL_SIZE = new TitledTabSizePolicy(1, "Individual Size");
+
+  /**
+   * An array with all size policies.
+   */
+  public static final TitledTabSizePolicy[] SIZE_POLICIES = new TitledTabSizePolicy[]{EQUAL_SIZE, INDIVIDUAL_SIZE};
+
+  private TitledTabSizePolicy(int value, String name) {
+    super(value, name);
+  }
+
+  /**
+   * Gets the size policies.
+   *
+   * @return the size policies
+   * @since ITP 1.1.0
+   */
+  public static TitledTabSizePolicy[] getSizePolicies() {
+    return (TitledTabSizePolicy[]) SIZE_POLICIES.clone();
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/titledtab/TitledTabSizePolicyProperty.java b/src/net/infonode/tabbedpanel/titledtab/TitledTabSizePolicyProperty.java
new file mode 100644
index 0000000..f80734e
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/titledtab/TitledTabSizePolicyProperty.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitledTabSizePolicyProperty.java,v 1.6 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel.titledtab;
+
+import net.infonode.properties.base.PropertyGroup;
+import net.infonode.properties.types.EnumProperty;
+import net.infonode.properties.util.PropertyValueHandler;
+
+/**
+ * Property for TitledTabSizePolicy
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ * @see TitledTabSizePolicy
+ */
+public class TitledTabSizePolicyProperty extends EnumProperty {
+  /**
+   * Constructs a TitledTabSizePolicyProperty object.
+   *
+   * @param group        property group
+   * @param name         property name
+   * @param description  property description
+   * @param valueStorage storage for property
+   */
+  public TitledTabSizePolicyProperty(PropertyGroup group,
+                                     String name,
+                                     String description,
+                                     PropertyValueHandler valueStorage) {
+    super(group, name, TitledTabSizePolicy.class, description, valueStorage, TitledTabSizePolicy.getSizePolicies());
+  }
+
+  /**
+   * Gets the TitledTabSizePolicy
+   *
+   * @param object storage object for property
+   * @return the TitledTabSizePolicy
+   */
+  public TitledTabSizePolicy get(Object object) {
+    return (TitledTabSizePolicy) getValue(object);
+  }
+
+  /**
+   * Sets the TitledTabSizePolicy
+   *
+   * @param object storage object for property
+   * @param policy the TitledTabSizePolicy
+   */
+  public void set(Object object, TitledTabSizePolicy policy) {
+    setValue(object, policy);
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/titledtab/TitledTabStateProperties.java b/src/net/infonode/tabbedpanel/titledtab/TitledTabStateProperties.java
new file mode 100644
index 0000000..61337be
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/titledtab/TitledTabStateProperties.java
@@ -0,0 +1,602 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: TitledTabStateProperties.java,v 1.24 2005/02/16 11:28:15 jesper Exp $
+package net.infonode.tabbedpanel.titledtab;
+
+import net.infonode.properties.gui.util.ComponentProperties;
+import net.infonode.properties.gui.util.ShapedPanelProperties;
+import net.infonode.properties.propertymap.*;
+import net.infonode.properties.types.*;
+import net.infonode.util.Alignment;
+import net.infonode.util.Direction;
+
+import javax.swing.*;
+
+/**
+ * TitledTabStateProperties holds all properties that are unique for a titled tab state.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.24 $
+ * @see TitledTab
+ * @see TitledTabProperties
+ */
+public class TitledTabStateProperties extends PropertyMapContainer {
+  /**
+   * A property group for all properties in TitledTabStateProperties
+   */
+  public static final PropertyMapGroup PROPERTIES = new PropertyMapGroup("State Properties", "");
+
+  /**
+   * Icon property
+   *
+   * @see #setIcon
+   * @see #getIcon
+   */
+  public static final IconProperty ICON = new IconProperty(PROPERTIES,
+                                                           "Icon",
+                                                           "Icon",
+                                                           PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Text property
+   *
+   * @see #setText
+   * @see #getText
+   */
+  public static final StringProperty TEXT = new StringProperty(PROPERTIES,
+                                                               "Text",
+                                                               "Text",
+                                                               PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Icon text gap property
+   *
+   * @see #setIconTextGap
+   * @see #getIconTextGap
+   */
+  public static final IntegerProperty ICON_TEXT_GAP = IntegerProperty.createPositive(PROPERTIES,
+                                                                                     "Icon Text Gap",
+                                                                                     "Number of pixels between icon and text.",
+                                                                                     2,
+                                                                                     PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tool tip text property
+   *
+   * @see #setToolTipText
+   * @see #getToolTipText
+   */
+  public static final StringProperty TOOL_TIP_TEXT = new StringProperty(PROPERTIES,
+                                                                        "Tool Tip Text",
+                                                                        "Tool tip text",
+                                                                        PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tool tip enabled property
+   *
+   * @see #setToolTipEnabled
+   * @see #getToolTipEnabled
+   */
+  public static final BooleanProperty TOOL_TIP_ENABLED = new BooleanProperty(PROPERTIES,
+                                                                             "Tool Tip Enabled",
+                                                                             "Tool tip enabled or disabled",
+                                                                             PropertyMapValueHandler.INSTANCE);
+
+
+  /**
+   * Icon visible property
+   *
+   * @see #setIconVisible
+   * @see #getIconVisible
+   * @since ITP 1.1.0
+   */
+  public static final BooleanProperty ICON_VISIBLE = new BooleanProperty(PROPERTIES,
+                                                                         "Icon Visible",
+                                                                         "Icon visible or not visible",
+                                                                         PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Text visible property
+   *
+   * @see #setTextVisible
+   * @see #getTextVisible
+   * @since ITP 1.1.0
+   */
+  public static final BooleanProperty TEXT_VISIBLE = new BooleanProperty(PROPERTIES,
+                                                                         "Text Visible",
+                                                                         "Text visible or not visible",
+                                                                         PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Title component visible property
+   *
+   * @see #setTitleComponentVisible
+   * @see #getTitleComponentVisible
+   * @since ITP 1.1.0
+   */
+  public static final BooleanProperty TITLE_COMPONENT_VISIBLE = new BooleanProperty(PROPERTIES,
+                                                                                    "Title Component Visible",
+                                                                                    "Title component visible or not visible",
+                                                                                    PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Horizontal alignment property
+   *
+   * @see #setHorizontalAlignment
+   * @see #getHorizontalAlignment
+   */
+  public static final AlignmentProperty HORIZONTAL_ALIGNMENT = new AlignmentProperty(PROPERTIES,
+                                                                                     "Horizontal Alignment",
+                                                                                     "Horizontal alignment for the icon and text.",
+                                                                                     PropertyMapValueHandler.INSTANCE,
+                                                                                     Alignment.getHorizontalAlignments());
+
+  /**
+   * Vertical alignment property
+   *
+   * @see #setVerticalAlignment
+   * @see #getVerticalAlignment
+   */
+  public static final AlignmentProperty VERTICAL_ALIGNMENT = new AlignmentProperty(PROPERTIES,
+                                                                                   "Vertical Alignment",
+                                                                                   "Vertical alignment for the icon and text.",
+                                                                                   PropertyMapValueHandler.INSTANCE,
+                                                                                   Alignment.getVerticalAlignments());
+
+  /**
+   * Icon text relative alignment property
+   *
+   * @see #setIconTextRelativeAlignment
+   * @see #getIconTextRelativeAlignment
+   */
+  public static final AlignmentProperty ICON_TEXT_RELATIVE_ALIGNMENT = new AlignmentProperty(PROPERTIES,
+                                                                                             "Icon Text Relative Alignment",
+                                                                                             "Icon horizontal alignment relative to text.",
+                                                                                             PropertyMapValueHandler.INSTANCE,
+                                                                                             new Alignment[]{
+                                                                                               Alignment.LEFT,
+                                                                                               Alignment.RIGHT});
+
+  /**
+   * Text title component gap property
+   *
+   * @see #setTextTitleComponentGap
+   * @see #getTextTitleComponentGap
+   */
+  public static final IntegerProperty TEXT_TITLE_COMPONENT_GAP = IntegerProperty.createPositive(PROPERTIES,
+                                                                                                "Text Title Component Gap",
+                                                                                                "Number of pixels between text and title component.",
+                                                                                                2,
+                                                                                                PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Title component text relative alignment property
+   *
+   * @see #setTitleComponentTextRelativeAlignment
+   * @see #getTitleComponentTextRelativeAlignment
+   */
+  public static final AlignmentProperty TITLE_COMPONENT_TEXT_RELATIVE_ALIGNMENT = new AlignmentProperty(PROPERTIES,
+                                                                                                        "Title Component Text Relative Alignment",
+                                                                                                        "Title component horizontal alignment relative to text and icon.",
+                                                                                                        PropertyMapValueHandler.INSTANCE,
+                                                                                                        new Alignment[]{
+                                                                                                          Alignment.LEFT,
+                                                                                                          Alignment.RIGHT});
+
+  /**
+   * Direction property
+   *
+   * @see #setDirection
+   * @see #getDirection
+   */
+  public static final DirectionProperty DIRECTION = new DirectionProperty(PROPERTIES,
+                                                                          "Direction",
+                                                                          "Direction for tab contents",
+                                                                          PropertyMapValueHandler.INSTANCE);
+
+  /**
+   * Tab component properties.
+   */
+  public static final PropertyMapProperty COMPONENT_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                         "Component Properties",
+                                                                                         "Tab component properties.",
+                                                                                         ComponentProperties.PROPERTIES);
+
+  /**
+   * Tab shaped panel properties.
+   *
+   * @since ITP 1.2.0
+   */
+  public static final PropertyMapProperty SHAPED_PANEL_PROPERTIES = new PropertyMapProperty(PROPERTIES,
+                                                                                            "Shaped Panel Properties",
+                                                                                            "Tab shaped panel properties.",
+                                                                                            ShapedPanelProperties.PROPERTIES);
+
+  /**
+   * Constructs an empty TitledTabStateProperties object
+   */
+  public TitledTabStateProperties() {
+    super(PROPERTIES);
+  }
+
+  /**
+   * Constructs a TitledTabStateProperties map with the give map as property storage
+   *
+   * @param map map to store properties in
+   */
+  public TitledTabStateProperties(PropertyMap map) {
+    super(map);
+  }
+
+  /**
+   * Constructs a TitledTabStateProperties object that inherits its properties from the given TitledTabStateProperties object
+   *
+   * @param inheritFrom TitledTabStateProperties object to inherit properties from
+   */
+  public TitledTabStateProperties(TitledTabStateProperties inheritFrom) {
+    super(PropertyMapFactory.create(inheritFrom.getMap()));
+  }
+
+  /**
+   * Adds a super object from which property values are inherited.
+   *
+   * @param superObject the object from which to inherit property values
+   * @return this
+   */
+  public TitledTabStateProperties addSuperObject(TitledTabStateProperties superObject) {
+    getMap().addSuperMap(superObject.getMap());
+    return this;
+  }
+
+
+  /**
+   * Removes the last added super object.
+   *
+   * @return this
+   */
+  public TitledTabStateProperties removeSuperObject() {
+    getMap().removeSuperMap();
+    return this;
+  }
+
+  /**
+   * Removes the given super object.
+   *
+   * @param superObject super object to remove
+   * @return this
+   * @since ITP 1.3.0
+   */
+  public TitledTabStateProperties removeSuperObject(TitledTabStateProperties superObject) {
+    getMap().removeSuperMap(superObject.getMap());
+    return this;
+  }
+
+  /**
+   * Sets the icon
+   *
+   * @param icon icon or null for no icon
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setIcon(Icon icon) {
+    ICON.set(getMap(), icon);
+    return this;
+  }
+
+  /**
+   * Gets the icon
+   *
+   * @return icon or null if no icon
+   */
+  public Icon getIcon() {
+    return ICON.get(getMap());
+  }
+
+  /**
+   * Sets the text
+   *
+   * @param text text or null for no text
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setText(String text) {
+    TEXT.set(getMap(), text);
+    return this;
+  }
+
+  /**
+   * Gets the text
+   *
+   * @return text or null if no text
+   */
+  public String getText() {
+    return TEXT.get(getMap());
+  }
+
+  /**
+   * Sets the gap in pixels between the icon and the text
+   *
+   * @param gap number of pixels
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setIconTextGap(int gap) {
+    ICON_TEXT_GAP.set(getMap(), gap);
+    return this;
+  }
+
+  /**
+   * Gets the gap in pixels between the icon and the text
+   *
+   * @return number of pixels
+   */
+  public int getIconTextGap() {
+    return ICON_TEXT_GAP.get(getMap());
+  }
+
+  /**
+   * Sets the tool tip text
+   *
+   * @param text tool tip text
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setToolTipText(String text) {
+    TOOL_TIP_TEXT.set(getMap(), text);
+    return this;
+  }
+
+  /**
+   * Gets the tool tip text
+   *
+   * @return tool tip text
+   */
+  public String getToolTipText() {
+    return TOOL_TIP_TEXT.get(getMap());
+  }
+
+  /**
+   * Sets if tool tip text is enabled or disabled
+   *
+   * @param enabled true for enabled, otherwise false
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setToolTipEnabled(boolean enabled) {
+    TOOL_TIP_ENABLED.set(getMap(), enabled);
+    return this;
+  }
+
+  /**
+   * Gets if tool tip text is enabled or disabled
+   *
+   * @return true if enabled, otherwise false
+   */
+  public boolean getToolTipEnabled() {
+    return TOOL_TIP_ENABLED.get(getMap());
+  }
+
+  /**
+   * Sets if icon is visible or not visible
+   *
+   * @param visible true for visible, otherwise false
+   * @return this TitledTabStateProperties
+   * @since ITP 1.1.0
+   */
+  public TitledTabStateProperties setIconVisible(boolean visible) {
+    ICON_VISIBLE.set(getMap(), visible);
+    return this;
+  }
+
+  /**
+   * Gets if icon is visible or not visible
+   *
+   * @return true if visible, otherwise false
+   * @since ITP 1.1.0
+   */
+  public boolean getIconVisible() {
+    return ICON_VISIBLE.get(getMap());
+  }
+
+  /**
+   * Sets if text is visible or not visible
+   *
+   * @param visible true for visible, otherwise false
+   * @return this TitledTabStateProperties
+   * @since ITP 1.1.0
+   */
+  public TitledTabStateProperties setTextVisible(boolean visible) {
+    TEXT_VISIBLE.set(getMap(), visible);
+    return this;
+  }
+
+  /**
+   * Gets if text is visible or not visible
+   *
+   * @return true if visible, otherwise false
+   * @since ITP 1.1.0
+   */
+  public boolean getTextVisible() {
+    return TEXT_VISIBLE.get(getMap());
+  }
+
+  /**
+   * Sets if title component is visible or not visible
+   *
+   * @param visible true for enabled, otherwise false
+   * @return this TitledTabStateProperties
+   * @since ITP 1.1.0
+   */
+  public TitledTabStateProperties setTitleComponentVisible(boolean visible) {
+    TITLE_COMPONENT_VISIBLE.set(getMap(), visible);
+    return this;
+  }
+
+  /**
+   * Gets if title component is visible or not visible
+   *
+   * @return true if enabled, otherwise false
+   * @since ITP 1.1.0
+   */
+  public boolean getTitleComponentVisible() {
+    return TITLE_COMPONENT_VISIBLE.get(getMap());
+  }
+
+  /**
+   * Sets the text's and icon's horizontal alignment
+   *
+   * @param alignment text and icon alignment
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setHorizontalAlignment(Alignment alignment) {
+    HORIZONTAL_ALIGNMENT.set(getMap(), alignment);
+    return this;
+  }
+
+  /**
+   * Gets the text's and icon's horizontal alignment
+   *
+   * @return text and icon alignment
+   */
+  public Alignment getHorizontalAlignment() {
+    return HORIZONTAL_ALIGNMENT.get(getMap());
+  }
+
+  /**
+   * Sets the text's and icon's vertical alignment
+   *
+   * @param alignment text and icon horizontal alignment
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setVerticalAlignment(Alignment alignment) {
+    VERTICAL_ALIGNMENT.set(getMap(), alignment);
+    return this;
+  }
+
+  /**
+   * Gets the text's and icon's vertical alignment
+   *
+   * @return text and icon vertical alignment
+   */
+  public Alignment getVerticalAlignment() {
+    return VERTICAL_ALIGNMENT.get(getMap());
+  }
+
+  /**
+   * Sets the icon alignment relative to the text. Makes it possible to switch
+   * places between text and icon.
+   *
+   * @param alignment icon alignment relative to text
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setIconTextRelativeAlignment(Alignment alignment) {
+    ICON_TEXT_RELATIVE_ALIGNMENT.set(getMap(), alignment);
+    return this;
+  }
+
+  /**
+   * Gets the icon alignment relative to the text.
+   *
+   * @return icon alignment relative to text
+   */
+  public Alignment getIconTextRelativeAlignment() {
+    return ICON_TEXT_RELATIVE_ALIGNMENT.get(getMap());
+  }
+
+  /**
+   * Sets the gap in pixels between the text/icon and the title component
+   *
+   * @param gap number of pixels
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setTextTitleComponentGap(int gap) {
+    TEXT_TITLE_COMPONENT_GAP.set(getMap(), gap);
+    return this;
+  }
+
+  /**
+   * Gets the gap in pixels between the text/icon and the title component
+   *
+   * @return number of pixels
+   */
+  public int getTextTitleComponentGap() {
+    return TEXT_TITLE_COMPONENT_GAP.get(getMap());
+  }
+
+  /**
+   * Sets the title components alignment relative to the text/icon
+   *
+   * @param alignment title component alignment relative to text/icon
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setTitleComponentTextRelativeAlignment(Alignment alignment) {
+    TITLE_COMPONENT_TEXT_RELATIVE_ALIGNMENT.set(getMap(), alignment);
+    return this;
+  }
+
+  /**
+   * Gets the title components alignment relative to the text/icon
+   *
+   * @return title component alignment relative to text/icon
+   */
+  public Alignment getTitleComponentTextRelativeAlignment() {
+    return TITLE_COMPONENT_TEXT_RELATIVE_ALIGNMENT.get(getMap());
+  }
+
+  /**
+   * Sets the direction, i.e. the line layout of the titled tab's components. The text
+   * and icon will be rotated in the given direction and the title component will be
+   * moved.
+   *
+   * @param direction direction
+   * @return this TitledTabStateProperties
+   */
+  public TitledTabStateProperties setDirection(Direction direction) {
+    DIRECTION.set(getMap(), direction);
+    return this;
+  }
+
+  /**
+   * Gets the direction, i.e. the line layout of the titled tab components. The text
+   * and icon are rotated in the given direction and the title component will be moved.
+   *
+   * @return direction
+   */
+  public Direction getDirection() {
+    return DIRECTION.get(getMap());
+  }
+
+  /**
+   * Gets the component properties.
+   *
+   * @return component properties
+   */
+  public ComponentProperties getComponentProperties() {
+    return new ComponentProperties(COMPONENT_PROPERTIES.get(getMap()));
+  }
+
+  /**
+   * Gets the shaped panel properties.
+   *
+   * @return shaped panel properties
+   * @since ITP 1.2.0
+   */
+  public ShapedPanelProperties getShapedPanelProperties() {
+    return new ShapedPanelProperties(SHAPED_PANEL_PROPERTIES.get(getMap()));
+  }
+}
diff --git a/src/net/infonode/tabbedpanel/titledtab/package.html b/src/net/infonode/tabbedpanel/titledtab/package.html
new file mode 100644
index 0000000..5392159
--- /dev/null
+++ b/src/net/infonode/tabbedpanel/titledtab/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+TitledTab is a tab type with support for text, icon and a custom Swing component
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/util/Alignment.java b/src/net/infonode/util/Alignment.java
new file mode 100644
index 0000000..478b464
--- /dev/null
+++ b/src/net/infonode/util/Alignment.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Alignment.java,v 1.5 2004/09/28 15:07:29 jesper Exp $
+package net.infonode.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * An enum class for alignments, left, center, right, top, bottom.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.5 $
+ */
+public final class Alignment extends Enum {
+  private static final long serialVersionUID = 4945539895437047593L;
+
+  /**
+   * Left alignment.
+   */
+  public static final Alignment LEFT = new Alignment(0, "Left");
+
+  /**
+   * Center alignment.
+   */
+  public static final Alignment CENTER = new Alignment(1, "Center");
+
+  /**
+   * Right alignment.
+   */
+  public static final Alignment RIGHT = new Alignment(2, "Right");
+
+  /**
+   * Top alignment.
+   */
+  public static final Alignment TOP = new Alignment(3, "Top");
+
+  /**
+   * Bottom alignment.
+   */
+  public static final Alignment BOTTOM = new Alignment(4, "Bottom");
+
+  /**
+   * Array containing all alignments..
+   */
+  public static final Alignment[] ALIGNMENTS = {LEFT, CENTER, RIGHT, TOP, BOTTOM};
+
+  /**
+   * Array containing all horizontal alignments..
+   */
+  public static final Alignment[] HORIZONTAL_ALIGNMENTS = {LEFT, CENTER, RIGHT};
+
+  /**
+   * Array containing all vertical alignments..
+   */
+  public static final Alignment[] VERTICAL_ALIGNMENTS = {TOP, CENTER, BOTTOM};
+
+  private Alignment(int value, String name) {
+    super(value, name);
+  }
+
+  /**
+   * Gets the alignments.
+   *
+   * @return the alignments
+   * @since 1.1.0
+   */
+  public static Alignment[] getAlignments() {
+    return (Alignment[]) ALIGNMENTS.clone();
+  }
+
+  /**
+   * Gets the horizontal alignments.
+   *
+   * @return the horizontal alignments
+   * @since 1.1.0
+   */
+  public static Alignment[] getHorizontalAlignments() {
+    return (Alignment[]) HORIZONTAL_ALIGNMENTS.clone();
+  }
+
+  /**
+   * Gets the vertical alignments.
+   *
+   * @return the vertical alignments
+   * @since 1.1.0
+   */
+  public static Alignment[] getVerticalAlignments() {
+    return (Alignment[]) VERTICAL_ALIGNMENTS.clone();
+  }
+
+  /**
+   * Decodes an alignment from a stream.
+   *
+   * @param in the stream containing the alignment
+   * @return the alignment
+   * @throws IOException if there is a stream error
+   */
+  public static Alignment decode(ObjectInputStream in) throws IOException {
+    return (Alignment) decode(Alignment.class, in);
+  }
+}
diff --git a/src/net/infonode/util/AntUtils.java b/src/net/infonode/util/AntUtils.java
new file mode 100644
index 0000000..81f8020
--- /dev/null
+++ b/src/net/infonode/util/AntUtils.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AntUtils.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.util;
+
+/**
+ * Utility functions for Ant build environment
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.2 $
+ */
+public class AntUtils {
+  public static ProductVersion createProductVersion(int major, int minor, int patch) {
+    return new ProductVersion(major, minor, patch);
+  }
+
+  public static ProductVersion createProductVersion(String major, String minor, String patch) {
+    return createProductVersion(0, 0, 0);
+  }
+
+  public static long getBuildTime(long time) {
+    return time;
+  }
+
+  public static long getBuildTime(String time) {
+    return getBuildTime(System.currentTimeMillis());
+  }
+}
diff --git a/src/net/infonode/util/ArrayUtil.java b/src/net/infonode/util/ArrayUtil.java
new file mode 100644
index 0000000..5fe2161
--- /dev/null
+++ b/src/net/infonode/util/ArrayUtil.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ArrayUtil.java,v 1.7 2005/01/28 13:50:29 jesper Exp $
+package net.infonode.util;
+
+import java.util.ArrayList;
+
+public class ArrayUtil {
+  private ArrayUtil() {
+  }
+
+  static final public Object[] add(Object[] objects, Object object, Object[] newObjects) {
+    System.arraycopy(objects, 0, newObjects, 0, objects.length);
+    newObjects[objects.length] = object;
+    return newObjects;
+  }
+
+  static final public byte[] part(byte[] array, int offset, int length) {
+    byte[] b = new byte[length];
+    System.arraycopy(array, offset, b, 0, length);
+    return b;
+  }
+
+  static final public int countNotNull(Object[] objects) {
+    int count = 0;
+
+    for (int i = 0; i < objects.length; i++)
+      if (objects[i] != null)
+        count++;
+
+    return count;
+  }
+
+  static final public int findSmallest(double[] items) {
+    int index = 0;
+
+    for (int i = 1; i < items.length; i++) {
+      if (items[i] < items[index]) {
+        index = i;
+      }
+    }
+
+    return index;
+  }
+
+  static final public int findSmallest(int[] items) {
+    int index = 0;
+
+    for (int i = 1; i < items.length; i++) {
+      if (items[i] < items[index]) {
+        index = i;
+      }
+    }
+
+    return index;
+  }
+
+  static final public int findLargest(float[] items) {
+    int index = 0;
+
+    for (int i = 1; i < items.length; i++) {
+      if (items[i] > items[index]) {
+        index = i;
+      }
+    }
+
+    return index;
+  }
+
+  public static float[] toFloatArray(int[] values) {
+    float[] floatValues = new float[values.length];
+
+    for (int i = 0; i < values.length; i++)
+      floatValues[i] = values[i];
+
+    return floatValues;
+  }
+
+  static final public int indexOf(int[] array, int value) {
+    for (int i = 0; i < array.length; i++)
+      if (array[i] == value)
+        return i;
+
+    return -1;
+  }
+
+  static final public int indexOf(byte[] array, byte value) {
+    for (int i = 0; i < array.length; i++)
+      if (array[i] == value)
+        return i;
+
+    return -1;
+  }
+
+  static final public String[] append(String[] a1, String[] a2) {
+    String[] n = new String[a1.length + a2.length];
+    System.arraycopy(a1, 0, n, 0, a1.length);
+    System.arraycopy(a2, 0, n, a1.length, a2.length);
+    return n;
+  }
+
+  static final public Object[] append(Object[] a1, Object[] a2, Object[] out) {
+    System.arraycopy(a1, 0, out, 0, a1.length);
+    System.arraycopy(a2, 0, out, a1.length, a2.length);
+    return out;
+  }
+
+  public static boolean equal(int[] a, int aOffset, int[] b, int bOffset, int length) {
+    for (int i = 0; i < length; i++)
+      if (a[aOffset + i] != b[bOffset + i])
+        return false;
+
+    return true;
+  }
+
+  public static boolean equal(byte[] a, int aOffset, byte[] b, int bOffset, int length) {
+    for (int i = 0; i < length; i++)
+      if (a[aOffset + i] != b[bOffset + i])
+        return false;
+
+    return true;
+  }
+
+  public static boolean contains(short[] a, short v) {
+    for (int i = 0; i < a.length; i++)
+      if (a[i] == v)
+        return true;
+
+    return false;
+  }
+
+  public static int[] range(int start, int length, int step) {
+    int[] a = new int[length];
+
+    for (int i = 0; i < length; i++)
+      a[i] = start + step * i;
+
+    return a;
+  }
+
+  public static boolean containsEqual(Object[] values, Object value) {
+    return indexOfEqual(values, value) != -1;
+  }
+
+  public static boolean contains(Object[] values, Object value) {
+    return indexOf(values, value) != -1;
+  }
+
+  public static int indexOf(Object[] values, Object value) {
+    for (int i = 0; i < values.length; i++)
+      if (values[i] == value)
+        return i;
+
+    return -1;
+  }
+
+  public static int indexOf(Object[] values, Object value, int startIndex, int length) {
+    for (int i = startIndex; i < length; i++)
+      if (values[i] == value)
+        return i;
+
+    return -1;
+  }
+
+  public static int indexOfEqual(Object[] values, Object value) {
+    for (int i = 0; i < values.length; i++)
+      if (values[i].equals(value))
+        return i;
+
+    return -1;
+  }
+
+  public static Object[] remove(Object[] values, Object value, Object[] newValues) {
+    int index = indexOf(values, value);
+
+    if (index == -1)
+      index = values.length;
+
+    System.arraycopy(values, 0, newValues, 0, index);
+    System.arraycopy(values, index + 1, newValues, index, newValues.length - index);
+    return newValues;
+  }
+
+  public static String toString(int[] a) {
+    StringBuffer b = new StringBuffer(a.length * 4);
+
+    for (int i = 0; i < a.length; i++) {
+      if (i != 0)
+        b.append(", ");
+
+      b.append(a[i]);
+    }
+
+    return b.toString();
+  }
+
+  public static int[] part(int[] values, int start, int length) {
+    int[] a = new int[length];
+    System.arraycopy(values, start, a, 0, length);
+    return a;
+  }
+
+  public static int sum(int[] values) {
+    int sum = 0;
+
+    for (int i = 0; i < values.length; i++)
+      sum += values[i];
+
+    return sum;
+  }
+
+  public static int count(int[] values, int value) {
+    int count = 0;
+
+    for (int i = 0; i < values.length; i++)
+      if (values[i] == value)
+        count++;
+
+    return count;
+  }
+
+  public static int count(boolean[] values, boolean value) {
+    int count = 0;
+
+    for (int i = 0; i < values.length; i++)
+      if (values[i] == value)
+        count++;
+
+    return count;
+  }
+
+  public static int findLargest(int[] items) {
+    int index = 0;
+
+    for (int i = 1; i < items.length; i++) {
+      if (items[i] > items[index]) {
+        index = i;
+      }
+    }
+
+    return index;
+  }
+
+  public static int[] toIntArray(ArrayList items) {
+    int[] result = new int[items.size()];
+
+    for (int i = 0; i < items.size(); i++)
+      result[i] = ((Number) items.get(i)).intValue();
+
+    return result;
+  }
+
+  public static boolean[] toBooleanArray(ArrayList items) {
+    boolean[] result = new boolean[items.size()];
+
+    for (int i = 0; i < items.size(); i++)
+      result[i] = ((Boolean) items.get(i)).booleanValue();
+
+    return result;
+  }
+}
diff --git a/src/net/infonode/util/ChangeNotifyList.java b/src/net/infonode/util/ChangeNotifyList.java
new file mode 100644
index 0000000..d4a7301
--- /dev/null
+++ b/src/net/infonode/util/ChangeNotifyList.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ChangeNotifyList.java,v 1.4 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.util;
+
+import java.util.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+abstract public class ChangeNotifyList implements List {
+  private List list;
+
+  abstract protected void changed();
+
+  protected ChangeNotifyList() {
+    this(new ArrayList(2));
+  }
+
+  protected ChangeNotifyList(List list) {
+    this.list = list;
+  }
+
+  protected List getList() {
+    return list;
+  }
+
+  public int size() {
+    return list.size();
+  }
+
+  public boolean isEmpty() {
+    return list.isEmpty();
+  }
+
+  public Object[] toArray() {
+    return list.toArray();
+  }
+
+  public Object get(int index) {
+    return list.get(index);
+  }
+
+  public int indexOf(Object o) {
+    return list.indexOf(o);
+  }
+
+  public int lastIndexOf(Object o) {
+    return list.lastIndexOf(o);
+  }
+
+  public boolean contains(Object o) {
+    return list.contains(o);
+  }
+
+  public boolean containsAll(Collection c) {
+    return list.containsAll(c);
+  }
+
+  public Iterator iterator() {
+    return listIterator();
+  }
+
+  public ListIterator listIterator() {
+    return listIterator(0);
+  }
+
+  public ListIterator listIterator(int index) {
+    return new ChangeIterator(list.listIterator(index));
+  }
+
+  public Object[] toArray(Object[] a) {
+    return list.toArray(a);
+  }
+
+  public void clear() {
+    list.clear();
+    changed();
+  }
+
+  public Object remove(int index) {
+    Object result = list.remove(index);
+    changed();
+    return result;
+  }
+
+  public void add(int index, Object element) {
+    list.add(index, element);
+    changed();
+  }
+
+  public boolean add(Object o) {
+    if (list.add(o)) {
+      changed();
+      return true;
+    }
+    else
+      return false;
+  }
+
+  public boolean remove(Object o) {
+    if (list.remove(o)) {
+      changed();
+      return true;
+    }
+    else
+      return false;
+  }
+
+  public boolean addAll(int index, Collection c) {
+    if (list.addAll(index, c)) {
+      changed();
+      return true;
+    }
+    else
+      return false;
+  }
+
+  public boolean addAll(Collection c) {
+    if (list.addAll(c)) {
+      changed();
+      return true;
+    }
+    else
+      return false;
+  }
+
+  public boolean removeAll(Collection c) {
+    if (list.removeAll(c)) {
+      changed();
+      return true;
+    }
+    else
+      return false;
+  }
+
+  public boolean retainAll(Collection c) {
+    if (list.retainAll(c)) {
+      changed();
+      return true;
+    }
+    else
+      return false;
+  }
+
+  public List subList(int fromIndex, int toIndex) {
+    return new ChangeNotifyList(list.subList(fromIndex, toIndex)) {
+      protected void changed() {
+        ChangeNotifyList.this.changed();
+      }
+    };
+  }
+
+  public Object set(int index, Object element) {
+    Object result = list.set(index, element);
+    changed();
+    return result;
+  }
+
+  private class ChangeIterator implements ListIterator {
+    private ListIterator iterator;
+
+    ChangeIterator(ListIterator iterator) {
+      this.iterator = iterator;
+    }
+
+    public int nextIndex() {
+      return iterator.nextIndex();
+    }
+
+    public int previousIndex() {
+      return iterator.previousIndex();
+    }
+
+    public boolean hasPrevious() {
+      return iterator.hasPrevious();
+    }
+
+    public Object previous() {
+      return iterator.previous();
+    }
+
+    public void add(Object o) {
+      iterator.add(o);
+      changed();
+    }
+
+    public void set(Object o) {
+      iterator.set(o);
+      changed();
+    }
+
+    public void remove() {
+      iterator.remove();
+      changed();
+    }
+
+    public boolean hasNext() {
+      return iterator.hasNext();
+    }
+
+    public Object next() {
+      return iterator.next();
+    }
+  }
+}
diff --git a/src/net/infonode/util/ColorUtil.java b/src/net/infonode/util/ColorUtil.java
new file mode 100644
index 0000000..f0dc815
--- /dev/null
+++ b/src/net/infonode/util/ColorUtil.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ColorUtil.java,v 1.10 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.util;
+
+import java.awt.*;
+
+public final class ColorUtil {
+  private ColorUtil() {
+  }
+
+  public static Color getOpposite(Color c) {
+    return isDark(c) ? Color.WHITE : Color.BLACK;
+  }
+
+  public static Color shade(Color c, double amount) {
+    return blend(c, getOpposite(c), amount);
+  }
+
+  public static final Color mult(Color c, double amount) {
+    return c == null ? null : new Color(Math.min(255, (int) (c.getRed() * amount)),
+                                        Math.min(255, (int) (c.getGreen() * amount)),
+                                        Math.min(255, (int) (c.getBlue() * amount)),
+                                        c.getAlpha());
+  }
+
+  public static Color setAlpha(Color c, int alpha) {
+    return c == null ? null : new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha);
+  }
+
+  public static final Color add(Color c1, Color c2) {
+    return c1 == null ? c2 :
+           c2 == null ? c1 :
+           new Color(Math.min(255, c1.getRed() + c2.getRed()),
+                     Math.min(255, c1.getGreen() + c2.getGreen()),
+                     Math.min(255, c1.getBlue() + c2.getBlue()),
+                     c1.getAlpha());
+  }
+
+  public static Color blend(Color c1, Color c2, double v) {
+    double v2 = 1 - v;
+    return c1 == null ? (c2 == null ? null : c2) :
+           c2 == null ? c1 :
+           new Color(Math.min(255, (int) (c1.getRed() * v2 + c2.getRed() * v)),
+                     Math.min(255, (int) (c1.getGreen() * v2 + c2.getGreen() * v)),
+                     Math.min(255, (int) (c1.getBlue() * v2 + c2.getBlue() * v)),
+                     Math.min(255, (int) (c1.getAlpha() * v2 + c2.getAlpha() * v)));
+  }
+
+  public static boolean isDark(Color c) {
+    return c.getRed() + c.getGreen() + c.getBlue() < 3 * 180;
+  }
+
+  public static Color highlight(Color c) {
+    return mult(c, isDark(c) ? 1.5F : 0.67F);
+  }
+
+  public static Color copy(Color c) {
+    return c == null ? null : new Color(c.getRed(), c.getGreen(), c.getBlue(), c.getAlpha());
+  }
+
+}
diff --git a/src/net/infonode/util/Direction.java b/src/net/infonode/util/Direction.java
new file mode 100644
index 0000000..3e4eac2
--- /dev/null
+++ b/src/net/infonode/util/Direction.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Direction.java,v 1.6 2004/09/28 15:07:29 jesper Exp $
+package net.infonode.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * An enum class for directions, up, down, left, right.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.6 $
+ */
+final public class Direction extends Enum {
+  private static final long serialVersionUID = 1;
+
+  /**
+   * Up direction.
+   */
+  public static final Direction UP = new Direction(0, "Up", false);
+
+  /**
+   * Right direction.
+   */
+  public static final Direction RIGHT = new Direction(1, "Right", true);
+
+  /**
+   * Down direction.
+   */
+  public static final Direction DOWN = new Direction(2, "Down", false);
+
+  /**
+   * Left direction.
+   */
+  public static final Direction LEFT = new Direction(3, "Left", true);
+
+  /**
+   * Array containing all directions.
+   */
+  public static final Direction[] DIRECTIONS = {UP, RIGHT, DOWN, LEFT};
+
+  static {
+    UP.rotateCW = RIGHT;
+    RIGHT.rotateCW = DOWN;
+    DOWN.rotateCW = LEFT;
+    LEFT.rotateCW = UP;
+  }
+
+  private transient Direction rotateCW;
+  private transient boolean isHorizontal;
+
+  private Direction(int value, String name, boolean isHorizontal) {
+    super(value, name);
+    this.isHorizontal = isHorizontal;
+  }
+
+  /**
+   * Returns the direction that is one quarter of a revolution clock wise.
+   *
+   * @return the direction that is one quarter of a revolution clock wise
+   */
+  public Direction getNextCW() {
+    return rotateCW;
+  }
+
+  /**
+   * Returns the direction that is one quarter of a revolution counter clock wise.
+   *
+   * @return the direction that is one quarter of a revolution counter clock wise
+   */
+  public Direction getNextCCW() {
+    return rotateCW.rotateCW.rotateCW;
+  }
+
+  /**
+   * Returns true if the direction is horizontal.
+   *
+   * @return true if the direction is horizontal
+   */
+  public boolean isHorizontal() {
+    return isHorizontal;
+  }
+
+  /**
+   * Returns the opposite direction.
+   *
+   * @return the opposite direction
+   */
+  public Direction getOpposite() {
+    return getNextCW().getNextCW();
+  }
+
+  /**
+   * Gets all directions.
+   *
+   * @return all directions
+   * @since 1.1.0
+   */
+  public static Direction[] getDirections() {
+    return (Direction[]) DIRECTIONS.clone();
+  }
+
+  /**
+   * Decodes a direction from a stream.
+   *
+   * @param in the stream containing the direction
+   * @return the direction
+   * @throws IOException if there is a stream error
+   */
+  public static Direction decode(ObjectInputStream in) throws IOException {
+    return (Direction) decode(Direction.class, in);
+  }
+
+}
diff --git a/src/net/infonode/util/Enum.java b/src/net/infonode/util/Enum.java
new file mode 100644
index 0000000..8b0a198
--- /dev/null
+++ b/src/net/infonode/util/Enum.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Enum.java,v 1.8 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.util;
+
+import java.io.*;
+import java.util.HashMap;
+
+/**
+ * Base class for enum classes.
+ * Each enum value contains a name and an integer identifier.
+ *
+ * @author Jesper Nordenberg
+ * @version $Revision: 1.8 $ $Date: 2005/02/16 11:28:14 $
+ */
+public class Enum implements Serializable, Writable {
+  private static final long serialVersionUID = 1;
+  private static final HashMap VALUE_MAP = new HashMap();
+
+  private int value;
+  private transient String name;
+
+  protected Enum(int value, String name) {
+    this.value = value;
+    this.name = name;
+
+    HashMap values = (HashMap) VALUE_MAP.get(getClass());
+
+    if (values == null) {
+      values = new HashMap();
+      VALUE_MAP.put(getClass(), values);
+    }
+
+    values.put(new Integer(value), this);
+  }
+
+  /**
+   * Returns the integer identifier for this enum value.
+   *
+   * @return the integer identifier for this enum value
+   */
+  public int getValue() {
+    return value;
+  }
+
+  /**
+   * Return the name of this enum value.
+   *
+   * @return the name of this enum value
+   */
+  public String getName() {
+    return name;
+  }
+
+  public String toString() {
+    return name;
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    writeObject(out);
+  }
+
+  protected static Object getObject(Class cl, int value) throws IOException {
+    HashMap map = (HashMap) VALUE_MAP.get(cl);
+
+    if (map == null)
+      throw new IOException("Invalid enum class '" + cl + "'!");
+
+    Object object = map.get(new Integer(value));
+
+    if (object == null)
+      throw new IOException("Invalid enum value '" + value + "'!");
+
+    return object;
+  }
+
+  private void writeObject(ObjectOutputStream out) throws IOException {
+    out.writeShort(value);
+  }
+
+  private void readObject(ObjectInputStream in) throws IOException {
+    value = in.readShort();
+  }
+
+  protected static Object decode(Class cl, ObjectInputStream in) throws IOException {
+    return getObject(cl, in.readShort());
+  }
+
+  protected Object readResolve() throws ObjectStreamException {
+    try {
+      return getObject(getClass(), getValue());
+    }
+    catch (IOException e) {
+      return this;
+    }
+  }
+}
diff --git a/src/net/infonode/util/ImageException.java b/src/net/infonode/util/ImageException.java
new file mode 100644
index 0000000..0e4e775
--- /dev/null
+++ b/src/net/infonode/util/ImageException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: ImageException.java,v 1.3 2004/11/05 17:53:08 johan Exp $
+package net.infonode.util;
+
+public class ImageException extends Exception {
+  public ImageException(String msg) {
+    super(msg);
+  }
+}
diff --git a/src/net/infonode/util/ImageUtils.java b/src/net/infonode/util/ImageUtils.java
new file mode 100644
index 0000000..fc7ae4f
--- /dev/null
+++ b/src/net/infonode/util/ImageUtils.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: ImageUtils.java,v 1.7 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.util; // Generated package name
+
+import net.infonode.util.math.Int4;
+
+import java.awt.*;
+import java.awt.Image;
+import java.awt.geom.AffineTransform;
+import java.awt.image.ImageObserver;
+import java.awt.image.PixelGrabber;
+import java.net.URL;
+
+public final class ImageUtils {
+  public static final Image create(String filename) throws ImageException {
+    Image image = Toolkit.getDefaultToolkit().createImage(filename);
+    waitImage(image);
+    return image;
+  }
+
+  public static final Image create(URL url) throws ImageException {
+    Image image = Toolkit.getDefaultToolkit().createImage(url);
+    waitImage(image);
+    return image;
+  }
+
+  public static final Image create(byte data[]) throws ImageException {
+    Image image = Toolkit.getDefaultToolkit().createImage(data);
+    waitImage(image);
+    return image;
+  }
+
+  public static final void waitImage(Image image) throws ImageException {
+    MediaTracker tracker = new MediaTracker(new Canvas()); //use dummy component
+    tracker.addImage(image, 1);
+
+    try {
+      tracker.waitForID(1);
+    }
+    catch (InterruptedException e) {
+      throw new ImageException("Interrupted when creating image!");
+    }
+  }
+
+  public static final int[] getPixels(Image image) throws ImageException {
+    return getPixels(image, 0, 0, image.getWidth(null), image.getHeight(null));
+  }
+
+  public static final int[] getPixels(Image image, int x, int y, int width, int height) throws ImageException {
+    int[] pixels = new int[width * height];
+    PixelGrabber pg = new PixelGrabber(image, x, y, width, height, pixels, 0, width);
+    try {
+      pg.grabPixels();
+    }
+    catch (InterruptedException e) {
+      throw new ImageException("Interrupted waiting for pixels");
+    }
+
+    if ((pg.getStatus() & ImageObserver.ABORT) != 0)
+      throw new ImageException("Image fetch aborted or errored");
+
+    return pixels;
+  }
+
+  public static final int getAlpha(int pixel) {
+    return (pixel >> 24) & 0xff;
+  }
+
+  public static final int getRed(int pixel) {
+    return (pixel >> 16) & 0xff;
+  }
+
+  public static final int getGreen(int pixel) {
+    return (pixel >> 8) & 0xff;
+  }
+
+  public static final int getBlue(int pixel) {
+    return pixel & 0xff;
+  }
+
+  public static final int createPixel(int red, int green, int blue) {
+    return (0xff << 24) | (red << 16) | (green << 8) | blue;
+  }
+
+  public static int toIntColor(Int4 i) {
+    return ((i.getD() << 8) & 0xff000000) |
+           (i.getA() & 0xff0000) |
+           ((i.getB() >> 8) & 0xff00) |
+           ((i.getC() >> 16) & 0xff);
+  }
+
+  public static Int4 toInt4(Color c) {
+    return new Int4(c.getRed() << 16, c.getGreen() << 16, c.getBlue() << 16, c.getAlpha() << 16);
+  }
+
+  public static Color toColor(Int4 c) {
+    return new Color(c.getA() >> 16, c.getB() >> 16, c.getC() >> 16, c.getD() >> 16);
+  }
+
+  public static final int[] createGradientPixels(Color[] colors, int width, int height) {
+    int[] pixels = new int[width * height];
+    createGradientPixels(colors, width, height, pixels);
+    return pixels;
+  }
+
+  public static final int[] createGradientPixels(Color[] colors, int width, int height, int[] pixels) {
+    int p = 0;
+
+    Int4 c1 = toInt4(colors[0]);
+    Int4 c2 = toInt4(colors[1]);
+    Int4 dc1 = toInt4(colors[2]).sub(toInt4(colors[0])).div(height);
+    Int4 dc2 = toInt4(colors[3]).sub(toInt4(colors[1])).div(height);
+    Int4 d = new Int4();
+    Int4 c = new Int4();
+
+    for (int y = 0; y < height; y++) {
+      d.set(c2).sub(c1).div(width);
+      c.set(c1);
+
+      for (int stop = p + width; p < stop; p++) {
+        pixels[p] = toIntColor(c);
+        c.add(d);
+      }
+
+      c1.add(dc1);
+      c2.add(dc2);
+    }
+
+    return pixels;
+  }
+
+  public static AffineTransform createTransform(Direction direction,
+                                                boolean horizontalFlip,
+                                                boolean verticalFlip,
+                                                int width,
+                                                int height) {
+    int hf = horizontalFlip ? -1 : 1;
+    int vf = verticalFlip ? -1 : 1;
+    int m00 = direction == Direction.RIGHT ? hf : direction == Direction.LEFT ? -hf : 0;
+    int m01 = direction == Direction.DOWN ? -vf : direction == Direction.UP ? vf : 0;
+    int m10 = direction == Direction.DOWN ? hf : direction == Direction.UP ? -hf : 0;
+    int m11 = direction == Direction.RIGHT ? vf : direction == Direction.LEFT ? -vf : 0;
+    return new AffineTransform(m00,
+                               m10,
+                               m01,
+                               m11,
+                               m00 == -1 ? width : m01 == -1 ? height : 0,
+                               m10 == -1 ? width : m11 == -1 ? height : 0);
+  }
+}
diff --git a/src/net/infonode/util/IntList.java b/src/net/infonode/util/IntList.java
new file mode 100644
index 0000000..6abbea0
--- /dev/null
+++ b/src/net/infonode/util/IntList.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: IntList.java,v 1.3 2004/09/22 14:35:05 jesper Exp $
+package net.infonode.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * A single linked list of positive int's.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class IntList {
+  /**
+   * The empty list.
+   */
+  public static final IntList EMPTY_LIST = new IntList(-1, null);
+
+  private int value;
+  private IntList next;
+
+  /**
+   * Constructor.
+   *
+   * @param value the int value
+   * @param next  the next list element
+   */
+  public IntList(int value, IntList next) {
+    this.value = value;
+    this.next = next;
+  }
+
+  /**
+   * @return
+   */
+  public int getValue() {
+    return value;
+  }
+
+  public IntList getNext() {
+    return next;
+  }
+
+  public boolean isEmpty() {
+    return this == EMPTY_LIST;
+  }
+
+  public boolean equals(Object object) {
+    return object instanceof IntList && equals((IntList) object);
+  }
+
+  public boolean equals(IntList list) {
+    return value == list.value && (next == null ? list.next == null : next.equals(list.next));
+  }
+
+  public int hashCode() {
+    int result = 17;
+    result = 37 * result + value;
+
+    if (next != null)
+      result = 37 * result + next.hashCode();
+
+    return result;
+  }
+
+  public void write(ObjectOutputStream out) throws IOException {
+    out.writeInt(value);
+
+    if (next != null)
+      next.write(out);
+  }
+
+  public static IntList decode(ObjectInputStream in) throws IOException {
+    int i = in.readInt();
+    return i == -1 ? EMPTY_LIST : new IntList(i, decode(in));
+  }
+
+  public String toString() {
+    return value + (next == null ? "" : ", " + next.toString());
+  }
+
+}
diff --git a/src/net/infonode/util/Printer.java b/src/net/infonode/util/Printer.java
new file mode 100644
index 0000000..b51dfcb
--- /dev/null
+++ b/src/net/infonode/util/Printer.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Printer.java,v 1.3 2005/02/01 17:18:20 jesper Exp $
+package net.infonode.util;
+
+import java.io.PrintStream;
+
+public class Printer {
+  private PrintStream out;
+  private String indent = "";
+  private boolean newLine = true;
+
+  public Printer() {
+    this(System.out);
+  }
+
+  public Printer(PrintStream out) {
+    this.out = out;
+  }
+
+  public void beginSection() {
+    indent += "  ";
+  }
+
+  public void beginSection(String title) {
+    println(title);
+    indent += "  ";
+  }
+
+  public void endSection() {
+    indent = indent.substring(2);
+  }
+
+  public void print(String str) {
+    if (newLine)
+      out.print(indent);
+
+    out.print(str);
+    newLine = false;
+  }
+
+  public void println(String str) {
+    if (newLine)
+      out.print(indent);
+
+    out.println(str);
+    newLine = true;
+  }
+
+  public void println() {
+    if (newLine)
+      out.print(indent);
+
+    out.println();
+    newLine = true;
+  }
+}
diff --git a/src/net/infonode/util/ProductVersion.java b/src/net/infonode/util/ProductVersion.java
new file mode 100644
index 0000000..cd066b2
--- /dev/null
+++ b/src/net/infonode/util/ProductVersion.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: ProductVersion.java,v 1.3 2004/09/28 14:57:39 jesper Exp $
+package net.infonode.util;
+
+import java.io.Serializable;
+
+/**
+ * A class that represents a product version
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class ProductVersion implements Serializable {
+  private static final long serialVersionUID = 1;
+
+  private int major;
+  private int minor;
+  private int patch;
+
+  /**
+   * Constructs a product version object
+   *
+   * @param major Major version number
+   * @param minor Minor version number
+   * @param patch Patch version number
+   */
+  public ProductVersion(int major, int minor, int patch) {
+    this.major = major;
+    this.minor = minor;
+    this.patch = patch;
+  }
+
+  /**
+   * Gets the major version number, i.e.
+   * the number X in version X.0.0
+   *
+   * @return Major version number
+   */
+  public int getMajor() {
+    return major;
+  }
+
+  /**
+   * Gets the minor version number, i.e.
+   * the number X in version 0.X.0
+   *
+   * @return Minor version number
+   */
+  public int getMinor() {
+    return minor;
+  }
+
+  /**
+   * Gets the patch version number, i.e.
+   * the number X in version 0.0.X
+   *
+   * @return Minor version number
+   */
+  public int getPatch() {
+    return patch;
+  }
+
+  /**
+   * Gets the version as string
+   *
+   * @return Version as string
+   */
+  public String toString() {
+    return major + "." + minor + "." + patch;
+  }
+}
diff --git a/src/net/infonode/util/ReadWritable.java b/src/net/infonode/util/ReadWritable.java
new file mode 100644
index 0000000..1de4303
--- /dev/null
+++ b/src/net/infonode/util/ReadWritable.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ReadWritable.java,v 1.4 2004/09/22 14:35:05 jesper Exp $
+package net.infonode.util;
+
+/**
+ * Interface for objects that can be read from and written to streams.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public interface ReadWritable extends Readable, Writable {
+}
diff --git a/src/net/infonode/util/Readable.java b/src/net/infonode/util/Readable.java
new file mode 100644
index 0000000..5b81f66
--- /dev/null
+++ b/src/net/infonode/util/Readable.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Readable.java,v 1.4 2004/07/06 15:08:45 jesper Exp $
+package net.infonode.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+
+/**
+ * Interface for objects that can be read from an {@link ObjectInputStream}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public interface Readable {
+  /**
+   * Reads this object from an ObjectInputStream.
+   *
+   * @param in the stream
+   * @throws IOException if there is a stream error
+   */
+  void read(ObjectInputStream in) throws IOException;
+}
diff --git a/src/net/infonode/util/ReleaseInfo.java b/src/net/infonode/util/ReleaseInfo.java
new file mode 100644
index 0000000..b075a1d
--- /dev/null
+++ b/src/net/infonode/util/ReleaseInfo.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+//$Id: ReleaseInfo.java,v 1.9 2005/02/16 11:28:14 jesper Exp $
+package net.infonode.util;
+
+import java.io.Serializable;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * A class that represents release information for a product
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.9 $
+ */
+public class ReleaseInfo implements Serializable {
+  private static final long serialVersionUID = 1;
+
+  private String productName;
+  private String productVendor;
+  private String license;
+  private long buildTime;
+  private ProductVersion productVersion;
+  private URL homepage;
+
+  /**
+   * Constructs a release info object
+   *
+   * @param name      product name
+   * @param vendor    vendor name
+   * @param buildTime time of nuild in millis
+   * @param version   product version
+   * @param license   the product license
+   * @param homepage  URL to the product homepage
+   */
+  public ReleaseInfo(String name,
+                     String vendor,
+                     long buildTime,
+                     ProductVersion version,
+                     String license,
+                     String homepage) {
+    this.productName = name;
+    this.productVendor = vendor;
+    this.buildTime = buildTime;
+    this.productVersion = version;
+    this.license = license;
+
+    try {
+      this.homepage = new URL(homepage);
+    }
+    catch (MalformedURLException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /**
+   * Gets the product name
+   *
+   * @return Product name
+   */
+  public String getProductName() {
+    return productName;
+  }
+
+  /**
+   * Gets the product vendor
+   *
+   * @return Product vendor
+   */
+  public String getProductVendor() {
+    return productVendor;
+  }
+
+  /**
+   * Gets the product license
+   *
+   * @return Product license
+   */
+  public String getLicense() {
+    return license;
+  }
+
+  /**
+   * Gets the build time in millis
+   *
+   * @return Build time in millis
+   */
+  public long getBuildTime() {
+    return buildTime;
+  }
+
+  /**
+   * Gets the product version
+   *
+   * @return Product version
+   */
+  public ProductVersion getProductVersion() {
+    return productVersion;
+  }
+
+  /**
+   * Gets the URL for the product homepage.
+   *
+   * @return the URL for the product homepage
+   */
+  public URL getHomepage() {
+    return homepage;
+  }
+
+}
diff --git a/src/net/infonode/util/StreamUtil.java b/src/net/infonode/util/StreamUtil.java
new file mode 100644
index 0000000..6a8ca62
--- /dev/null
+++ b/src/net/infonode/util/StreamUtil.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: StreamUtil.java,v 1.7 2005/02/16 11:28:14 jesper Exp $
+
+package net.infonode.util;
+
+import java.io.*;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.7 $
+ */
+public class StreamUtil {
+  public static final void readAll(InputStream in, byte[] data, int offset, int length) throws IOException {
+    while (length > 0) {
+      int len = in.read(data, offset, length);
+
+      if (len == -1)
+        throw new IOException("End of stream reached!");
+
+      offset += len;
+      length -= len;
+    }
+  }
+
+  public static final byte[] readAll(InputStream is) throws IOException {
+    byte[] data = new byte[is.available()];
+    int pos = 0;
+
+    while (pos < data.length)
+      pos += is.read(data, pos, data.length - pos);
+
+    is.close();
+    return data;
+  }
+
+  public static final byte[] writeObject(Object object) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    ObjectOutputStream o2 = new ObjectOutputStream(out);
+    o2.writeObject(object);
+    o2.close();
+    return ArrayUtil.part(out.toByteArray(), 0, out.size());
+  }
+
+  public static final Object readObject(byte[] data) throws IOException, ClassNotFoundException {
+    return new ObjectInputStream(new ByteArrayInputStream(data)).readObject();
+  }
+
+  public static byte[] write(Writable writable) throws IOException {
+    ByteArrayOutputStream out = new ByteArrayOutputStream();
+    ObjectOutputStream o2 = new ObjectOutputStream(out);
+    writable.write(o2);
+    o2.close();
+    return out.toByteArray();
+  }
+
+  public static void read(byte[] data, Readable readable) throws IOException {
+    readable.read(new ObjectInputStream(new ByteArrayInputStream(data)));
+  }
+
+  public static void readAll(InputStream in, byte[] data) throws IOException {
+    readAll(in, data, 0, data.length);
+  }
+
+  public static void write(InputStream in, OutputStream out, int length) throws IOException {
+    byte[] data = new byte[10000];
+
+    while (length > 0) {
+      int read = in.read(data, 0, Math.min(data.length, length));
+      out.write(data, 0, read);
+      length -= read;
+    }
+  }
+}
diff --git a/src/net/infonode/util/Utils.java b/src/net/infonode/util/Utils.java
new file mode 100644
index 0000000..9641813
--- /dev/null
+++ b/src/net/infonode/util/Utils.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Utils.java,v 1.4 2005/01/26 12:50:31 jesper Exp $
+package net.infonode.util;
+
+public class Utils {
+  private Utils() {
+  }
+
+  public static final short unsigned(byte b) {
+    return (short) (b & 0xff);
+  }
+
+  public static final boolean equals(Object o1, Object o2) {
+    return o1 == o2 || (o1 != null && o2 != null && o1.equals(o2));
+  }
+
+}
diff --git a/src/net/infonode/util/ValueChange.java b/src/net/infonode/util/ValueChange.java
new file mode 100644
index 0000000..01917b4
--- /dev/null
+++ b/src/net/infonode/util/ValueChange.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ValueChange.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.util;
+
+/**
+ * A value change.
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.2 $
+ */
+public class ValueChange {
+  private Object oldValue;
+  private Object newValue;
+
+  /**
+   * Constructor.
+   *
+   * @param oldValue the old value
+   * @param newValue the new value
+   */
+  public ValueChange(Object oldValue, Object newValue) {
+    this.oldValue = oldValue;
+    this.newValue = newValue;
+  }
+
+  /**
+   * Returns the old value.
+   *
+   * @return the old value
+   */
+  public Object getOldValue() {
+    return oldValue;
+  }
+
+  /**
+   * Returns the new value.
+   *
+   * @return the new value
+   */
+  public Object getNewValue() {
+    return newValue;
+  }
+}
diff --git a/src/net/infonode/util/Writable.java b/src/net/infonode/util/Writable.java
new file mode 100644
index 0000000..1afc29a
--- /dev/null
+++ b/src/net/infonode/util/Writable.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Writable.java,v 1.4 2004/07/06 15:08:45 jesper Exp $
+package net.infonode.util;
+
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
+/**
+ * Interface for objects that can be written to an {@link java.io.ObjectOutputStream}.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public interface Writable {
+  /**
+   * Writes this object to an ObjectOutputStream.
+   *
+   * @param out the stream
+   * @throws IOException if there is a stream error
+   */
+  void write(ObjectOutputStream out) throws IOException;
+}
diff --git a/src/net/infonode/util/collection/Closure.java b/src/net/infonode/util/collection/Closure.java
new file mode 100644
index 0000000..cc34f50
--- /dev/null
+++ b/src/net/infonode/util/collection/Closure.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Closure.java,v 1.2 2004/09/20 13:26:39 johan Exp $
+package net.infonode.util.collection;
+
+/**
+ * @author $Author: johan $
+ * @version $Revision: 1.2 $
+ */
+public interface Closure {
+  void apply(Object object);
+}
diff --git a/src/net/infonode/util/collection/Collection.java b/src/net/infonode/util/collection/Collection.java
new file mode 100644
index 0000000..a1bb355
--- /dev/null
+++ b/src/net/infonode/util/collection/Collection.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Collection.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.util.collection;
+
+public interface Collection extends ConstCollection {
+  /**
+   * Removes all elements from this collection..
+   */
+  void clear();
+}
diff --git a/src/net/infonode/util/collection/ConstCollection.java b/src/net/infonode/util/collection/ConstCollection.java
new file mode 100644
index 0000000..4db0955
--- /dev/null
+++ b/src/net/infonode/util/collection/ConstCollection.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ConstCollection.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.util.collection;
+
+public interface ConstCollection {
+  /**
+   * Returns true if this collection is empty.
+   *
+   * @return true if this collection is empty
+   */
+  boolean isEmpty();
+
+}
diff --git a/src/net/infonode/util/collection/CopyOnWriteArrayList.java b/src/net/infonode/util/collection/CopyOnWriteArrayList.java
new file mode 100644
index 0000000..e2bffaf
--- /dev/null
+++ b/src/net/infonode/util/collection/CopyOnWriteArrayList.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: CopyOnWriteArrayList.java,v 1.3 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.util.collection;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public final class CopyOnWriteArrayList {
+  private static class IteratorImpl implements Iterator {
+    private Object[] e;
+    private int size;
+    private int index;
+
+    IteratorImpl(Object[] e, int size, int index) {
+      this.e = e;
+      this.size = size;
+      this.index = index;
+    }
+
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+
+    public boolean hasNext() {
+      return index < size;
+    }
+
+    public Object next() {
+      return e[index++];
+    }
+  }
+
+  private Object[] elements;
+  private int size;
+
+  public CopyOnWriteArrayList(int initialCapacity) {
+    elements = new Object[initialCapacity];
+  }
+
+  public void removeAll(Collection toRemove) {
+    Object[] ne = new Object[size - toRemove.size()];
+    int j = 0;
+
+    for (int i = 0; i < size; i++) {
+      if (!toRemove.contains(elements[i])) {
+        ne[j++] = elements[i];
+      }
+    }
+
+    size = j;
+    elements = ne;
+  }
+
+  public void add(Object element) {
+    if (size >= elements.length) {
+      Object[] newElements = new Object[getPreferredSize(size)];
+      System.arraycopy(elements, 0, newElements, 0, size);
+      elements = newElements;
+    }
+
+    elements[size++] = element;
+  }
+
+  public boolean remove(Object element) {
+    int index = indexOf(element);
+
+    if (index == -1)
+      return false;
+
+    remove(index);
+    return true;
+  }
+
+  public void remove(int index) {
+    size--;
+    Object[] newElements = new Object[getPreferredSize(size)];
+    System.arraycopy(elements, 0, newElements, 0, index);
+    System.arraycopy(elements, index + 1, newElements, index, size - index);
+    elements = newElements;
+  }
+
+  public int indexOf(Object element) {
+    for (int i = 0; i < size; i++)
+      if (elements[i] == element)
+        return i;
+
+    return -1;
+  }
+
+  public void each(Closure closure) {
+    Object[] l = elements;
+    int s = size;
+
+    for (int i = 0; i < s; i++)
+      closure.apply(l[i]);
+  }
+
+  public Iterator iterator() {
+    return new IteratorImpl(elements, size, 0);
+  }
+
+  private static int getPreferredSize(int size) {
+    return size * 3 / 2 + 1;
+  }
+
+  public int size() {
+    return size;
+  }
+
+  public Object get(int index) {
+    return elements[index];
+  }
+
+  public Object[] getElements() {
+    return elements;
+  }
+
+/*  private static class Iterator implements java.util.Iterator {
+    private Object[] elements;
+    private int size;
+    private int index;
+
+    Iterator(Object[] elements, int size) {
+      this.elements = elements;
+      this.size = size;
+    }
+
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+
+    public boolean hasNext() {
+      return index < size;
+    }
+
+    public Object next() {
+      return elements[index++];
+    }
+  }*/
+
+}
diff --git a/src/net/infonode/util/collection/EmptyIterator.java b/src/net/infonode/util/collection/EmptyIterator.java
new file mode 100644
index 0000000..21a2da0
--- /dev/null
+++ b/src/net/infonode/util/collection/EmptyIterator.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: EmptyIterator.java,v 1.2 2005/05/13 15:07:02 johan Exp $
+package net.infonode.util.collection;
+
+import java.util.Iterator;
+
+/**
+ * @author $Author: johan $
+ * @version $Revision: 1.2 $
+ */
+public class EmptyIterator implements Iterator {
+  public static final EmptyIterator INSTANCE = new EmptyIterator();
+
+  private EmptyIterator() {
+  }
+
+  public void remove() {
+    throw new UnsupportedOperationException();
+  }
+
+  public boolean hasNext() {
+    return false;
+  }
+
+  public Object next() {
+    return null;
+  }
+}
diff --git a/src/net/infonode/util/collection/map/ConstVectorMap.java b/src/net/infonode/util/collection/map/ConstVectorMap.java
new file mode 100644
index 0000000..44f2c17
--- /dev/null
+++ b/src/net/infonode/util/collection/map/ConstVectorMap.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ConstVectorMap.java,v 1.8 2005/03/17 16:13:03 jesper Exp $
+package net.infonode.util.collection.map;
+
+import net.infonode.util.collection.map.base.ConstMap;
+import net.infonode.util.collection.map.base.ConstMapIterator;
+
+import java.util.ArrayList;
+
+public class ConstVectorMap implements ConstMap {
+  private class ConstIterator implements ConstMapIterator {
+    private int index = 1;
+    private ConstMapIterator iterator;
+
+    ConstIterator() {
+      if (maps.size() > 0) {
+        iterator = getMap(0).constIterator();
+        advance();
+      }
+      else
+        iterator = EmptyIterator.INSTANCE;
+    }
+
+    public Object getKey() {
+      return iterator.getKey();
+    }
+
+    public Object getValue() {
+      return iterator.getValue();
+    }
+
+    public void next() {
+      iterator.next();
+      advance();
+    }
+
+    public boolean atEntry() {
+      return iterator.atEntry();
+    }
+
+    private void advance() {
+      while (true) {
+        while (!iterator.atEntry()) {
+          if (index == maps.size())
+            return;
+
+          iterator = getMap(index++).constIterator();
+        }
+
+        if (ConstVectorMap.this.getValue(iterator.getKey(), 0, index - 1) == null)
+          return;
+
+        iterator.next();
+      }
+    }
+  }
+
+  private ArrayList maps = new ArrayList(2);
+
+  private Object getValue(Object key, int fromIndex, int toIndex) {
+    for (int i = fromIndex; i < toIndex; i++) {
+      Object value = getMap(i).get(key);
+
+      if (value != null)
+        return value;
+    }
+
+    return null;
+  }
+
+  public void addMap(ConstMap map) {
+    addMap(maps.size(), map);
+  }
+
+  public void addMap(int index, final ConstMap map) {
+    maps.add(index, map);
+  }
+
+  public int getMapCount() {
+    return maps.size();
+  }
+
+  public ConstMap removeMap(int index) {
+    return (ConstMap) maps.remove(index);
+  }
+
+  public Object get(Object key) {
+    for (int i = 0; i < maps.size(); i++) {
+      Object v = getMap(i).get(key);
+
+      if (v != null)
+        return v;
+    }
+
+    return null;
+  }
+
+  public boolean containsKey(Object key) {
+    for (int i = 0; i < maps.size(); i++) {
+      if (getMap(i).containsKey(key))
+        return true;
+    }
+
+    return false;
+  }
+
+  public boolean containsValue(Object value) {
+    for (int i = 0; i < maps.size(); i++) {
+      if (getMap(i).containsValue(value))
+        return true;
+    }
+
+    return false;
+  }
+
+  public boolean isEmpty() {
+    for (int i = 0; i < maps.size(); i++) {
+      if (!getMap(i).isEmpty())
+        return false;
+    }
+
+    return true;
+  }
+
+  public ConstMap getMap(int index) {
+    return (ConstMap) maps.get(index);
+  }
+
+  public int getMapIndex(ConstMap map) {
+    return maps.indexOf(map);
+  }
+
+  public ConstMapIterator constIterator() {
+    return new ConstIterator();
+  }
+}
diff --git a/src/net/infonode/util/collection/map/EmptyIterator.java b/src/net/infonode/util/collection/map/EmptyIterator.java
new file mode 100644
index 0000000..435e6f6
--- /dev/null
+++ b/src/net/infonode/util/collection/map/EmptyIterator.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: EmptyIterator.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.util.collection.map;
+
+import net.infonode.util.collection.map.base.MapIterator;
+
+public class EmptyIterator implements MapIterator {
+  public static final EmptyIterator INSTANCE = new EmptyIterator();
+
+  private EmptyIterator() {
+  }
+
+  public void remove() {
+  }
+
+  public Object getKey() {
+    return null;
+  }
+
+  public Object getValue() {
+    return null;
+  }
+
+  public void next() {
+  }
+
+  public boolean atEntry() {
+    return false;
+  }
+}
diff --git a/src/net/infonode/util/collection/map/MapAdapter.java b/src/net/infonode/util/collection/map/MapAdapter.java
new file mode 100644
index 0000000..c2e0e2e
--- /dev/null
+++ b/src/net/infonode/util/collection/map/MapAdapter.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: MapAdapter.java,v 1.6 2005/02/16 11:28:13 jesper Exp $
+package net.infonode.util.collection.map;
+
+import net.infonode.util.collection.map.base.ConstMapIterator;
+import net.infonode.util.collection.map.base.Map;
+import net.infonode.util.collection.map.base.MapIterator;
+
+import java.util.HashMap;
+
+public class MapAdapter implements Map {
+  private static class Iterator implements MapIterator {
+    private java.util.Iterator iterator;
+    private java.util.Map.Entry nextEntry;
+
+    Iterator(java.util.Iterator iterator) {
+      this.iterator = iterator;
+      next();
+    }
+
+    public void remove() {
+      iterator.remove();
+    }
+
+    public boolean atEntry() {
+      return nextEntry != null;
+    }
+
+    public Object getKey() {
+      return nextEntry.getKey();
+    }
+
+    public Object getValue() {
+      return nextEntry.getValue();
+    }
+
+    public void next() {
+      nextEntry = iterator.hasNext() ? (java.util.Map.Entry) iterator.next() : null;
+    }
+  }
+
+  private HashMap map;
+
+  public MapAdapter() {
+  }
+
+  public MapAdapter(HashMap map) {
+    this.map = map;
+  }
+
+  public Object put(Object key, Object value) {
+    if (map == null)
+      map = new HashMap(4);
+
+    return map.put(key, value);
+  }
+
+  public Object remove(Object key) {
+    return map == null ? null : map.remove(key);
+  }
+
+  public void clear() {
+    if (map != null)
+      map.clear();
+  }
+
+  public MapIterator iterator() {
+    return map == null ? (MapIterator) EmptyIterator.INSTANCE : (MapIterator) new Iterator(map.entrySet().iterator());
+  }
+
+  public Object get(Object key) {
+    return map == null ? null : map.get(key);
+  }
+
+  public boolean containsKey(Object key) {
+    return map != null && map.containsKey(key);
+  }
+
+  public boolean containsValue(Object value) {
+    return map != null && map.containsValue(value);
+  }
+
+  public boolean isEmpty() {
+    return map == null || map.isEmpty();
+  }
+
+  public ConstMapIterator constIterator() {
+    return iterator();
+  }
+
+  public int size() {
+    return map == null ? 0 : map.size();
+  }
+}
diff --git a/src/net/infonode/util/collection/map/SingleValueMap.java b/src/net/infonode/util/collection/map/SingleValueMap.java
new file mode 100644
index 0000000..1af1008
--- /dev/null
+++ b/src/net/infonode/util/collection/map/SingleValueMap.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SingleValueMap.java,v 1.3 2005/02/16 10:21:02 johan Exp $
+package net.infonode.util.collection.map;
+
+import net.infonode.util.Utils;
+import net.infonode.util.collection.map.base.ConstMap;
+import net.infonode.util.collection.map.base.ConstMapIterator;
+
+/**
+ * @author $Author: johan $
+ * @version $Revision: 1.3 $
+ */
+public class SingleValueMap implements ConstMap {
+  private Object key;
+  private Object value;
+
+  public SingleValueMap(Object key, Object value) {
+    this.key = key;
+    this.value = value;
+  }
+
+  public Object get(Object key) {
+    return Utils.equals(key, this.key) ? value : null;
+  }
+
+  public boolean containsKey(Object key) {
+    return Utils.equals(key, this.key);
+  }
+
+  public boolean containsValue(Object value) {
+    return Utils.equals(value, this.value);
+  }
+
+  public ConstMapIterator constIterator() {
+    return new ConstMapIterator() {
+      private boolean done;
+
+      public Object getKey() {
+        return key;
+      }
+
+      public Object getValue() {
+        return value;
+      }
+
+      public void next() {
+        done = true;
+      }
+
+      public boolean atEntry() {
+        return !done;
+      }
+    };
+  }
+
+  public boolean isEmpty() {
+    return false;
+  }
+
+}
diff --git a/src/net/infonode/util/collection/map/base/ConstMap.java b/src/net/infonode/util/collection/map/base/ConstMap.java
new file mode 100644
index 0000000..67df75b
--- /dev/null
+++ b/src/net/infonode/util/collection/map/base/ConstMap.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ConstMap.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.util.collection.map.base;
+
+import net.infonode.util.collection.ConstCollection;
+
+/**
+ * An immutable map.
+ *
+ * @author $Author: johan $
+ * @version $Revision: 1.2 $
+ */
+public interface ConstMap extends ConstCollection {
+  /**
+   * Returns the value associated with the key.
+   *
+   * @param key the key
+   * @return the value associated with the key, null if no value is associated with the key
+   */
+  Object get(Object key);
+
+  /**
+   * Returns true if this map contains the key.
+   *
+   * @param key the key
+   * @return true if this map contains the key
+   */
+  boolean containsKey(Object key);
+
+  /**
+   * Returns true if this map contains the value.
+   *
+   * @param value the value
+   * @return true if this map contains the value
+   */
+  boolean containsValue(Object value);
+
+  /**
+   * Returns an iterator for this map.
+   *
+   * @return an iterator for this map
+   */
+  ConstMapIterator constIterator();
+}
diff --git a/src/net/infonode/util/collection/map/base/ConstMapIterator.java b/src/net/infonode/util/collection/map/base/ConstMapIterator.java
new file mode 100644
index 0000000..add0593
--- /dev/null
+++ b/src/net/infonode/util/collection/map/base/ConstMapIterator.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ConstMapIterator.java,v 1.3 2004/09/22 14:35:05 jesper Exp $
+package net.infonode.util.collection.map.base;
+
+/**
+ * An iterator for a map.
+ * The iterator points to a map entry when it's created so {@link #next} shouldn't be called at the start of the
+ * iteration.
+ * <p>
+ * Here's an example on how to iterate over a map:
+ * <code>
+ * for (ConstIterator iterator = map.constIterator(); iterator.atEntry(); iterator.next()) {
+ * Object key = iterator.getKey();
+ * Object value = iterator.getValue();
+ * ...
+ * }
+ * </code>
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public interface ConstMapIterator {
+  /**
+   * Returns the key at the current map entry.
+   *
+   * @return the key at the current map entry
+   */
+  Object getKey();
+
+  /**
+   * Returns the value at the current map entry.
+   *
+   * @return the value at the current map entry
+   */
+  Object getValue();
+
+  /**
+   * Advance the iterator to the next entry.
+   */
+  void next();
+
+  /**
+   * Returns true if the iterator points to an entry in the map.
+   *
+   * @return true if the iterator points to an entry in the map
+   */
+  boolean atEntry();
+}
diff --git a/src/net/infonode/util/collection/map/base/Map.java b/src/net/infonode/util/collection/map/base/Map.java
new file mode 100644
index 0000000..8827b2c
--- /dev/null
+++ b/src/net/infonode/util/collection/map/base/Map.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Map.java,v 1.4 2004/09/22 14:35:05 jesper Exp $
+package net.infonode.util.collection.map.base;
+
+import net.infonode.util.collection.Collection;
+
+/**
+ * A map.
+ *
+ * @author $Author: jesper $
+ * @version $Revision: 1.4 $
+ */
+public interface Map extends ConstMap, Collection {
+  /**
+   * Associate a key with a value.
+   * This will overwrite any existing association.
+   *
+   * @param key   the key
+   * @param value the value
+   * @return the old value associated with this key, null if no value existed
+   */
+  Object put(Object key, Object value);
+
+  /**
+   * Removes a key and it's value.
+   *
+   * @param key the key
+   * @return the value associated with the key, null if no value existed
+   */
+  Object remove(Object key);
+
+  /**
+   * Returns an iterator for this map.
+   *
+   * @return an iterator for this map
+   */
+  MapIterator iterator();
+}
diff --git a/src/net/infonode/util/collection/map/base/MapIterator.java b/src/net/infonode/util/collection/map/base/MapIterator.java
new file mode 100644
index 0000000..324744e
--- /dev/null
+++ b/src/net/infonode/util/collection/map/base/MapIterator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: MapIterator.java,v 1.4 2004/09/22 14:35:05 jesper Exp $
+package net.infonode.util.collection.map.base;
+
+
+public interface MapIterator extends ConstMapIterator {
+  void remove();
+}
diff --git a/src/net/infonode/util/collection/notifymap/AbstractChangeNotifyMap.java b/src/net/infonode/util/collection/notifymap/AbstractChangeNotifyMap.java
new file mode 100644
index 0000000..2b7c65f
--- /dev/null
+++ b/src/net/infonode/util/collection/notifymap/AbstractChangeNotifyMap.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractChangeNotifyMap.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.util.collection.notifymap;
+
+import net.infonode.util.collection.map.base.ConstMapIterator;
+
+abstract public class AbstractChangeNotifyMap extends AbstractConstChangeNotifyMap implements ChangeNotifyMap {
+  public ConstMapIterator constIterator() {
+    return iterator();
+  }
+
+}
diff --git a/src/net/infonode/util/collection/notifymap/AbstractConstChangeNotifyMap.java b/src/net/infonode/util/collection/notifymap/AbstractConstChangeNotifyMap.java
new file mode 100644
index 0000000..ae606bb
--- /dev/null
+++ b/src/net/infonode/util/collection/notifymap/AbstractConstChangeNotifyMap.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: AbstractConstChangeNotifyMap.java,v 1.14 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.util.collection.notifymap;
+
+import net.infonode.util.ValueChange;
+import net.infonode.util.collection.map.SingleValueMap;
+import net.infonode.util.collection.map.base.ConstMap;
+import net.infonode.util.signal.Signal;
+import net.infonode.util.signal.SignalHook;
+import net.infonode.util.signal.SignalListener;
+
+abstract public class AbstractConstChangeNotifyMap implements ConstChangeNotifyMap {
+  private Signal changeSignal = new Signal() {
+    protected void firstListenerAdded() {
+      AbstractConstChangeNotifyMap.this.firstListenerAdded();
+    }
+
+    protected void lastListenerRemoved() {
+      AbstractConstChangeNotifyMap.this.lastListenerRemoved();
+    }
+
+    protected synchronized void removeListener(int index) {
+      super.removeListener(index);
+      listenerRemoved();
+    }
+
+    public synchronized void addListener(SignalListener listener) {
+      super.addListener(listener);
+      listenerAdded();
+    }
+  };
+
+  protected void firstListenerAdded() {
+  }
+
+  protected void lastListenerRemoved() {
+  }
+
+  protected void listenerRemoved() {
+  }
+
+  protected void listenerAdded() {
+  }
+
+  public SignalHook getChangeSignal() {
+    return changeSignal.getHook();
+  }
+
+  protected Signal getChangeSignalInternal() {
+    return changeSignal;
+  }
+
+  protected void fireEntryRemoved(Object key, Object value) {
+    fireEntryChanged(key, value, null);
+  }
+
+  protected void fireEntryChanged(Object key, Object oldValue, Object newValue) {
+    fireEntriesChanged(new SingleValueMap(key, new ValueChange(oldValue, newValue)));
+  }
+
+  protected void fireEntriesChanged(ConstMap changes) {
+    if (!changes.isEmpty())
+      changeSignal.emit(changes);
+  }
+
+}
diff --git a/src/net/infonode/util/collection/notifymap/ChangeNotifyMap.java b/src/net/infonode/util/collection/notifymap/ChangeNotifyMap.java
new file mode 100644
index 0000000..1ad37b9
--- /dev/null
+++ b/src/net/infonode/util/collection/notifymap/ChangeNotifyMap.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ChangeNotifyMap.java,v 1.2 2004/06/17 13:01:11 johan Exp $
+package net.infonode.util.collection.notifymap;
+
+import net.infonode.util.collection.map.base.Map;
+
+public interface ChangeNotifyMap extends ConstChangeNotifyMap, Map {
+}
diff --git a/src/net/infonode/util/collection/notifymap/ChangeNotifyMapWrapper.java b/src/net/infonode/util/collection/notifymap/ChangeNotifyMapWrapper.java
new file mode 100644
index 0000000..9c3c918
--- /dev/null
+++ b/src/net/infonode/util/collection/notifymap/ChangeNotifyMapWrapper.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ChangeNotifyMapWrapper.java,v 1.3 2004/07/06 15:08:44 jesper Exp $
+package net.infonode.util.collection.notifymap;
+
+import net.infonode.util.ValueChange;
+import net.infonode.util.collection.map.MapAdapter;
+import net.infonode.util.collection.map.base.ConstMapIterator;
+import net.infonode.util.collection.map.base.Map;
+import net.infonode.util.collection.map.base.MapIterator;
+
+public class ChangeNotifyMapWrapper extends AbstractChangeNotifyMap {
+  private class Iterator implements MapIterator {
+    private MapIterator iterator;
+
+    public Iterator(MapIterator iterator) {
+      this.iterator = iterator;
+    }
+
+    public void remove() {
+      iterator.remove();
+      fireEntryRemoved(iterator.getKey(), iterator.getValue());
+    }
+
+    public Object getKey() {
+      return iterator.getKey();
+    }
+
+    public Object getValue() {
+      return iterator.getValue();
+    }
+
+    public void next() {
+      iterator.next();
+    }
+
+    public boolean atEntry() {
+      return iterator.atEntry();
+    }
+  }
+
+  private Map map;
+
+  public ChangeNotifyMapWrapper(Map map) {
+    this.map = map;
+  }
+
+  public Map getMap() {
+    return map;
+  }
+
+  public Object get(Object key) {
+    return map.get(key);
+  }
+
+  public boolean containsKey(Object key) {
+    return map.containsKey(key);
+  }
+
+  public boolean containsValue(Object value) {
+    return map.containsValue(value);
+  }
+
+  public boolean isEmpty() {
+    return map.isEmpty();
+  }
+
+  public Object put(Object key, Object value) {
+    Object oldValue = map.put(key, value);
+    fireEntryChanged(key, oldValue, value);
+    return oldValue;
+  }
+
+  public Object remove(Object key) {
+    Object oldValue = map.remove(key);
+    fireEntryRemoved(key, oldValue);
+    return oldValue;
+  }
+
+  public void clear() {
+    MapAdapter changeMap = new MapAdapter();
+
+    for (ConstMapIterator iterator = map.constIterator(); iterator.atEntry(); iterator.next()) {
+      changeMap.put(iterator.getKey(), new ValueChange(iterator.getValue(), null));
+    }
+
+    map.clear();
+    fireEntriesChanged(changeMap);
+  }
+
+  public MapIterator iterator() {
+    return new Iterator(map.iterator());
+  }
+}
diff --git a/src/net/infonode/util/collection/notifymap/ConstChangeNotifyMap.java b/src/net/infonode/util/collection/notifymap/ConstChangeNotifyMap.java
new file mode 100644
index 0000000..e6aec37
--- /dev/null
+++ b/src/net/infonode/util/collection/notifymap/ConstChangeNotifyMap.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ConstChangeNotifyMap.java,v 1.7 2005/03/17 16:13:03 jesper Exp $
+package net.infonode.util.collection.notifymap;
+
+import net.infonode.util.collection.map.base.ConstMap;
+import net.infonode.util.signal.SignalHook;
+
+public interface ConstChangeNotifyMap extends ConstMap {
+  SignalHook getChangeSignal();
+}
diff --git a/src/net/infonode/util/collection/notifymap/ConstChangeNotifyVectorMap.java b/src/net/infonode/util/collection/notifymap/ConstChangeNotifyVectorMap.java
new file mode 100644
index 0000000..3297024
--- /dev/null
+++ b/src/net/infonode/util/collection/notifymap/ConstChangeNotifyVectorMap.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: ConstChangeNotifyVectorMap.java,v 1.11 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.util.collection.notifymap;
+
+import net.infonode.util.ValueChange;
+import net.infonode.util.collection.map.ConstVectorMap;
+import net.infonode.util.collection.map.MapAdapter;
+import net.infonode.util.collection.map.base.ConstMap;
+import net.infonode.util.collection.map.base.ConstMapIterator;
+import net.infonode.util.signal.Signal;
+import net.infonode.util.signal.SignalListener;
+
+import java.util.ArrayList;
+
+public class ConstChangeNotifyVectorMap extends AbstractConstChangeNotifyMap {
+  private ConstVectorMap vectorMap = new ConstVectorMap();
+  private ArrayList mapListeners;
+
+  protected void firstListenerAdded() {
+    mapListeners = new ArrayList(vectorMap.getMapCount() + 2);
+
+    for (int i = 0; i < vectorMap.getMapCount(); i++) {
+      addMapListener(i);
+    }
+  }
+
+  protected void lastListenerRemoved() {
+    for (int i = vectorMap.getMapCount() - 1; i >= 0; i--) {
+      removeMapListener(i);
+    }
+
+    mapListeners = null;
+  }
+
+  private Object getValue(Object key, int fromIndex, int toIndex) {
+    for (int i = fromIndex; i < toIndex; i++) {
+      Object value = getMap(i).get(key);
+
+      if (value != null)
+        return value;
+    }
+
+    return null;
+  }
+
+  public int getMapIndex(ConstMap map) {
+    return vectorMap.getMapIndex(map);
+  }
+
+  public void addMap(ConstChangeNotifyMap map) {
+    addMap(vectorMap.getMapCount(), map);
+  }
+
+  public void addMap(int index, ConstChangeNotifyMap map) {
+    vectorMap.addMap(index, map);
+
+    if (getChangeSignalInternal().hasListeners()) {
+      addMapListener(index);
+      MapAdapter changes = new MapAdapter();
+
+      for (ConstMapIterator iterator = map.constIterator(); iterator.atEntry(); iterator.next()) {
+        Object value = getValue(iterator.getKey(), 0, index);
+
+        if (value == null) {
+          Object mapValue = iterator.getValue();
+          changes.put(iterator.getKey(),
+                      new ValueChange(getValue(iterator.getKey(), index + 1, getMapCount()), mapValue));
+        }
+      }
+
+      fireEntriesChanged(changes);
+    }
+  }
+
+  private void addMapListener(int index) {
+    if (mapListeners == null)
+      mapListeners = new ArrayList(index + 2);
+
+    final ConstChangeNotifyMap map = getMap(index);
+
+    SignalListener mapListener = new SignalListener() {
+      public void signalEmitted(Signal signal, Object object) {
+        ConstMap changes = (ConstMap) object;
+        MapAdapter changes2 = new MapAdapter();
+        int index = getMapIndex(map);
+
+        for (ConstMapIterator iterator = changes.constIterator(); iterator.atEntry(); iterator.next()) {
+          Object value = getValue(iterator.getKey(), 0, index);
+
+          if (value == null) {
+            ValueChange vc = (ValueChange) iterator.getValue();
+            changes2.put(iterator.getKey(), vc.getOldValue() == null ? new ValueChange(
+                getValue(iterator.getKey(), index + 1, getMapCount()), vc.getNewValue()) :
+                                            vc.getNewValue() == null ? new ValueChange(vc.getOldValue(),
+                                                                                       getValue(iterator.getKey(),
+                                                                                                index + 1,
+                                                                                                getMapCount())) :
+                                            vc);
+          }
+        }
+
+        fireEntriesChanged(changes2);
+      }
+    };
+
+    mapListeners.add(index, mapListener);
+    map.getChangeSignal().add(mapListener);
+  }
+
+  private void removeMapListener(int index) {
+    ConstChangeNotifyMap map = getMap(index);
+    map.getChangeSignal().remove((SignalListener) mapListeners.get(index));
+    mapListeners.remove(index);
+  }
+
+  public int getMapCount() {
+    return vectorMap.getMapCount();
+  }
+
+  public void removeMap(int index) {
+    if (getChangeSignalInternal().hasListeners())
+      removeMapListener(index);
+
+    ConstMap map = vectorMap.removeMap(index);
+
+    if (getChangeSignalInternal().hasListeners()) {
+      MapAdapter changes = new MapAdapter();
+
+      for (ConstMapIterator iterator = map.constIterator(); iterator.atEntry(); iterator.next()) {
+        Object value = getValue(iterator.getKey(), 0, index);
+
+        if (value == null) {
+          Object mapValue = iterator.getValue();
+          changes.put(iterator.getKey(), new ValueChange(mapValue, getValue(iterator.getKey(), index, getMapCount())));
+        }
+      }
+
+      fireEntriesChanged(changes);
+    }
+  }
+
+  public Object get(Object key) {
+    return vectorMap.get(key);
+  }
+
+  public boolean containsKey(Object key) {
+    return vectorMap.containsKey(key);
+  }
+
+  public boolean containsValue(Object value) {
+    return vectorMap.containsValue(value);
+  }
+
+  public boolean isEmpty() {
+    return vectorMap.isEmpty();
+  }
+
+  public ConstChangeNotifyMap getMap(int index) {
+    return (ConstChangeNotifyMap) vectorMap.getMap(index);
+  }
+
+  public ConstMapIterator constIterator() {
+    return vectorMap.constIterator();
+  }
+}
diff --git a/src/net/infonode/util/math/Int4.java b/src/net/infonode/util/math/Int4.java
new file mode 100644
index 0000000..ddd137e
--- /dev/null
+++ b/src/net/infonode/util/math/Int4.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Int4.java,v 1.2 2004/11/05 17:53:08 johan Exp $
+package net.infonode.util.math;
+
+/**
+ * @author $Author: johan $
+ * @version $Revision: 1.2 $
+ */
+final public class Int4 {
+  private int a;
+  private int b;
+  private int c;
+  private int d;
+
+  public Int4() {
+    this(0, 0, 0, 0);
+  }
+
+  public Int4(Int4 i) {
+    this(i.a, i.b, i.c, i.d);
+  }
+
+  public Int4(int a, int b, int c, int d) {
+    this.a = a;
+    this.b = b;
+    this.c = c;
+    this.d = d;
+  }
+
+  public int getA() {
+    return a;
+  }
+
+  public void setA(int a) {
+    this.a = a;
+  }
+
+  public int getB() {
+    return b;
+  }
+
+  public void setB(int b) {
+    this.b = b;
+  }
+
+  public int getC() {
+    return c;
+  }
+
+  public void setC(int c) {
+    this.c = c;
+  }
+
+  public int getD() {
+    return d;
+  }
+
+  public void setD(int d) {
+    this.d = d;
+  }
+
+  public Int4 set(Int4 i) {
+    a = i.a;
+    b = i.b;
+    c = i.c;
+    d = i.d;
+    return this;
+  }
+
+  public Int4 add(Int4 i) {
+    a += i.a;
+    b += i.b;
+    c += i.c;
+    d += i.d;
+    return this;
+  }
+
+  public Int4 sub(Int4 i) {
+    a -= i.a;
+    b -= i.b;
+    c -= i.c;
+    d -= i.d;
+    return this;
+  }
+
+  public Int4 div(long value) {
+    a /= value;
+    b /= value;
+    c /= value;
+    d /= value;
+    return this;
+  }
+
+  public Int4 mul(long value) {
+    a *= value;
+    b *= value;
+    c *= value;
+    d *= value;
+    return this;
+  }
+
+  public String toString() {
+    return a + ", " + b + ", " + c + ", " + d;
+  }
+
+}
diff --git a/src/net/infonode/util/package.html b/src/net/infonode/util/package.html
new file mode 100644
index 0000000..4185362
--- /dev/null
+++ b/src/net/infonode/util/package.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+Common utility classes
+</body>
+</html>
\ No newline at end of file
diff --git a/src/net/infonode/util/signal/Signal.java b/src/net/infonode/util/signal/Signal.java
new file mode 100644
index 0000000..8c9d207
--- /dev/null
+++ b/src/net/infonode/util/signal/Signal.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: Signal.java,v 1.3 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.util.signal;
+
+import net.infonode.util.collection.CopyOnWriteArrayList;
+import net.infonode.util.collection.EmptyIterator;
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public class Signal {
+  private static class WeakListener extends WeakReference implements SignalListener {
+    private SignalHookImpl hook;
+
+    protected WeakListener(SignalListener listener, ReferenceQueue q, SignalHookImpl hook) {
+      super(listener, q);
+      this.hook = hook;
+    }
+
+    public void remove() {
+      hook.removeWeak(this);
+    }
+
+    public void signalEmitted(Signal signal, Object object) {
+      SignalListener l = (SignalListener) get();
+
+      if (l != null)
+        l.signalEmitted(signal, object);
+    }
+  }
+
+  private class SignalHookImpl implements SignalHook {
+    public void add(SignalListener listener) {
+      addListener(listener);
+    }
+
+    public void addWeak(SignalListener listener) {
+      addListener(new WeakListener(listener, refQueue, this));
+    }
+
+    public boolean remove(SignalListener listener) {
+      return removeListener(listener);
+    }
+
+    public void removeWeak(WeakListener ref) {
+      removeWeakListener(ref);
+    }
+  }
+
+  private static ReferenceQueue refQueue = new ReferenceQueue();
+
+  static {
+    Thread thread = new Thread(new Runnable() {
+      public void run() {
+        try {
+          while (true) {
+            ((WeakListener) refQueue.remove()).remove();
+          }
+        }
+        catch (InterruptedException e) {
+        }
+      }
+    });
+    thread.setDaemon(true);
+    thread.start();
+  }
+
+  private boolean reverseNotifyOrder;
+  private CopyOnWriteArrayList listeners;
+  private SignalHookImpl signalHook = new SignalHookImpl();
+
+  public Signal() {
+    this(true);
+  }
+
+  public Signal(boolean reverseNotifyOrder) {
+    this.reverseNotifyOrder = reverseNotifyOrder;
+  }
+
+  protected void firstListenerAdded() {
+  }
+
+  protected void lastListenerRemoved() {
+  }
+
+  public synchronized void addListener(SignalListener listener) {
+    if (listeners == null)
+      listeners = new CopyOnWriteArrayList(2);
+
+    listeners.add(listener);
+
+    if (listeners.size() == 1)
+      firstListenerAdded();
+  }
+
+  public synchronized boolean removeListener(SignalListener listener) {
+    if (listeners != null) {
+      for (int i = 0; i < listeners.size(); i++) {
+        Object o = listeners.get(i);
+
+        if (o == listener || (o instanceof WeakListener && ((WeakListener) o).get() == listener)) {
+          removeListener(i);
+          return true;
+        }
+      }
+    }
+
+    return false;
+  }
+
+  protected synchronized void removeWeakListener(WeakListener listener) {
+    if (listeners != null) {
+      for (int i = 0; i < listeners.size(); i++) {
+        Object o = listeners.get(i);
+
+        if (o == listener) {
+          removeListener(i);
+          break;
+        }
+      }
+    }
+  }
+
+  protected synchronized void removeListener(int index) {
+    listeners.remove(index);
+
+    if (listeners.size() == 0) {
+      listeners = null;
+      lastListenerRemoved();
+    }
+  }
+
+  public synchronized boolean hasListeners() {
+    return listeners != null && listeners.size() > 0;
+  }
+
+  public synchronized Iterator iterator() {
+    return listeners == null ? EmptyIterator.INSTANCE : listeners.iterator();
+  }
+
+  public SignalHook getHook() {
+    return signalHook;
+  }
+
+  public synchronized void emit(Object object) {
+    Object[] e;
+    int size;
+
+    synchronized (this) {
+      if (listeners == null)
+        return;
+
+      e = listeners.getElements();
+      size = listeners.size();
+    }
+
+    if (reverseNotifyOrder) {
+      for (int i = size - 1; i >= 0; i--)
+        ((SignalListener) e[i]).signalEmitted(this, object);
+    }
+    else {
+      for (int i = 0; i < size; i++)
+        ((SignalListener) e[i]).signalEmitted(this, object);
+    }
+  }
+
+  public void removeListeners(Collection toRemove) {
+    listeners.removeAll(toRemove);
+  }
+
+}
diff --git a/src/net/infonode/util/signal/SignalHook.java b/src/net/infonode/util/signal/SignalHook.java
new file mode 100644
index 0000000..f15e81b
--- /dev/null
+++ b/src/net/infonode/util/signal/SignalHook.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SignalHook.java,v 1.3 2005/12/04 13:46:04 jesper Exp $
+package net.infonode.util.signal;
+
+/**
+ * @author $Author: jesper $
+ * @version $Revision: 1.3 $
+ */
+public interface SignalHook {
+  void add(SignalListener listener);
+
+  void addWeak(SignalListener listener);
+
+  boolean remove(SignalListener listener);
+}
diff --git a/src/net/infonode/util/signal/SignalListener.java b/src/net/infonode/util/signal/SignalListener.java
new file mode 100644
index 0000000..9520b2d
--- /dev/null
+++ b/src/net/infonode/util/signal/SignalListener.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2004 NNL Technology AB
+ * Visit www.infonode.net for information about InfoNode(R) 
+ * products and how to contact NNL Technology AB.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, 
+ * MA 02111-1307, USA.
+ */
+
+
+// $Id: SignalListener.java,v 1.2 2005/05/13 15:07:02 johan Exp $
+package net.infonode.util.signal;
+
+/**
+ * @author $Author: johan $
+ * @version $Revision: 1.2 $
+ */
+public interface SignalListener {
+  void signalEmitted(Signal signal, Object object);
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/libidw-java.git



More information about the pkg-java-commits mailing list